/**
 * Gutenberg block editor UI for RSM Content Widgets.
 */
( function ( wp ) {
	if ( ! wp || ! wp.blocks || ! wp.element ) {
		return;
	}

	var config =
		window.rsmsparkContentWidgetsBlocks ||
		{};
	var blocks = config.blocks || [];
	var blockAttributes =
		window.rsmsparkContentWidgetsBlockAttributes ||
		{};
	var i18n = config.i18n || {};

	var registerBlockType = wp.blocks.registerBlockType;
	var createElement = wp.element.createElement;
	var Fragment = wp.element.Fragment;
	var useState = wp.element.useState;
	var useEffect = wp.element.useEffect;
	var useRef = wp.element.useRef;
	var useSelect = wp.data && wp.data.useSelect;
	var useBlockProps = wp.blockEditor.useBlockProps;
	var useBlockEditContext = wp.blockEditor.useBlockEditContext;
	var InspectorControls = wp.blockEditor.InspectorControls;
	var MediaUpload = wp.blockEditor.MediaUpload;
	var MediaUploadCheck = wp.blockEditor.MediaUploadCheck;
	var PanelBody = wp.components.PanelBody;
	var TextControl = wp.components.TextControl;
	var TextareaControl = wp.components.TextareaControl;
	var SelectControl = wp.components.SelectControl;
	var Button = wp.components.Button;
	var Modal = wp.components.Modal;
	var BaseControl = wp.components.BaseControl;
	var ColorPalette = wp.components.ColorPalette;
	var ToggleControl = wp.components.ToggleControl;
	var RangeControl = wp.components.RangeControl;
	var Notice = wp.components.Notice;
	var Spinner = wp.components.Spinner;
	var apiFetch = wp.apiFetch;
	var ServerSideRender =
		wp.serverSideRender ||
		( wp.components && wp.components.ServerSideRender );

	function t( key, fallback ) {
		return i18n[ key ] || fallback;
	}

	function brandButtonClass( variant, extra ) {
		var base =
			variant === 'primary'
				? 'rsmspark-cw-button--brand'
				: 'rsmspark-cw-button--brand-secondary';
		return extra ? base + ' ' + extra : base;
	}

	/**
	 * True when Gutenberg is rendering the block inserter hover card (not the post canvas).
	 *
	 * @param {Object} context Block edit context.
	 * @param {Object} [props] Block edit props.
	 * @return {boolean}
	 */
	function isInserterHoverPreview( context, props ) {
		context = context || {};

		if ( context.isBlockPreview ) {
			return true;
		}

		if ( props && props.isPreview ) {
			return true;
		}

		return false;
	}

	/**
	 * Compact front-end SSR for the block inserter hover panel.
	 *
	 * @param {string} blockName Block name.
	 * @param {Object} attributes Block attributes.
	 * @return {import('react').ReactElement}
	 */
	function inserterOutputPreview( blockName, attributes ) {
		return blockLivePreview( blockName, attributes, true );
	}

	function blockSettingsCanvasHelp() {
		return t(
			'blockSettingsCanvasHelp',
			'Settings below also appear in the block sidebar when it is open.'
		);
	}

	function useIsBlockSidebarOpen() {
		if ( ! useSelect ) {
			return false;
		}

		return useSelect( function ( select ) {
			try {
				var iface = select( 'core/interface' );
				if ( ! iface || ! iface.getActiveComplementaryArea ) {
					return false;
				}

				var active = iface.getActiveComplementaryArea( 'core' );
				return (
					active === 'edit-post/block' ||
					active === 'edit-site/block-inspector'
				);
			} catch ( e ) {
				return false;
			}
		}, [] );
	}

	function BlockCanvasSettingsSection( props ) {
		var sidebarOpen = useIsBlockSidebarOpen();
		var openState = useState( false );
		var isOpen = openState[ 0 ];
		var setIsOpen = openState[ 1 ];
		var wasSidebarOpen = useRef( false );

		useEffect(
			function () {
				if ( sidebarOpen && ! wasSidebarOpen.current ) {
					setIsOpen( false );
				}
				wasSidebarOpen.current = sidebarOpen;
			},
			[ sidebarOpen ]
		);

		if ( ! props.panels ) {
			return null;
		}

		var classSuffix = props.classSuffix || '';

		if ( ! isOpen ) {
			return createElement(
				'div',
				{
					className:
						'rsmspark-cw-block-editor__settings-shell rsmspark-cw-block-editor__settings-shell--' +
						classSuffix +
						' rsmspark-cw-block-editor__settings-shell--collapsed',
				},
				sidebarOpen
					? createElement(
							Notice,
							{
								status: 'info',
								isDismissible: false,
								className:
									'rsmspark-cw-block-editor__canvas-settings-notice',
							},
							t(
								'blockSettingsInSidebar',
								'Block settings are open in the sidebar. Expand below to edit them here too.'
							)
					  )
					: null,
				createElement(
					Button,
					{
						variant: 'secondary',
						className: brandButtonClass(
							'secondary',
							'rsmspark-cw-block-editor__settings-toggle'
						),
						onClick: function () {
							setIsOpen( true );
						},
					},
					props.jsonContext
						? t(
								'showBlockSettingsJson',
								'Show import/export & settings'
						  )
						: t( 'showBlockSettings', 'Show block settings' )
				)
			);
		}

		return createElement(
			'div',
			{
				className:
					'rsmspark-cw-block-editor__settings-shell rsmspark-cw-block-editor__settings-shell--' +
					classSuffix +
					' rsmspark-cw-block-editor__settings-shell--panels',
			},
			createElement(
				'div',
				{ className: 'rsmspark-cw-block-editor__settings-head' },
				createElement(
					Button,
					{
						variant: 'secondary',
						className: brandButtonClass(
							'secondary',
							'rsmspark-cw-block-editor__settings-toggle'
						),
						onClick: function () {
							setIsOpen( false );
						},
					},
					props.jsonContext
						? t(
								'hideBlockSettingsJson',
								'Hide import/export & settings'
						  )
						: t( 'hideBlockSettings', 'Hide block settings' )
				)
			),
			props.jsonContext
				? createElement( BlockLayoutBandControls, {
						key:
							'rsmspark-cw-layout-' +
							( props.jsonContext.clientId ||
								props.jsonContext.blockName ||
								'default' ),
						jsonContext: props.jsonContext,
				  } )
				: null,
			props.jsonContext
				? createElement( BlockConfigJsonControls, {
						key:
							'rsmspark-cw-json-' +
							( props.jsonContext.clientId ||
								props.jsonContext.blockName ||
								'default' ),
						jsonContext: props.jsonContext,
				  } )
				: null,
			createElement(
				Notice,
				{
					status: 'info',
					isDismissible: false,
					className: 'rsmspark-cw-block-editor__canvas-settings-notice',
				},
				blockSettingsCanvasHelp()
			),
			createElement(
				'div',
				{
					className:
						'rsmspark-cw-block-editor__settings rsmspark-cw-block-editor__settings--' +
						classSuffix,
				},
				props.panels
			)
		);
	}

	function blockCanvasSettingsSection( classSuffix, panels, jsonContext ) {
		return createElement( BlockCanvasSettingsSection, {
			classSuffix: classSuffix,
			panels: panels,
			jsonContext: jsonContext || null,
		} );
	}

	function resolveCwBlockName( props, fallback ) {
		return (
			( props && props.name ) ||
			fallback ||
			'rsm-content-widgets/headline'
		);
	}

	function blockJsonContext( props, blockName ) {
		if ( ! props || ! blockName ) {
			return null;
		}

		return {
			blockName: blockName,
			clientId: props.clientId || '',
			attributes: props.attributes,
			setAttributes: props.setAttributes,
		};
	}

	function layoutBandFields( ctx ) {
		var attrs = ctx.attributes || {};
		var setAttributes = ctx.setAttributes;

		if ( ! setAttributes ) {
			return null;
		}

		return createElement(
			Fragment,
			null,
			createElement( SelectControl, {
				label: t( 'bandWidth', 'Background width' ),
				value: attrs.rsmBand || '',
				options: [
					{
						label: t(
							'bandWidthDefault',
							'Content width (default)'
						),
						value: '',
					},
					{
						label: t( 'bandWidthFull', 'Full screen band' ),
						value: 'full',
					},
				],
				help: t(
					'bandWidthHelp',
					'Full screen stretches the background edge-to-edge. Content stays inside the theme content width.'
				),
				onChange: function ( value ) {
					setAttributes( { rsmBand: value || '' } );
				},
			} ),
			( attrs.rsmBand || '' ) === 'full' &&
				createElement(
					BaseControl,
					{ label: t( 'bandBackground', 'Band background' ) },
					createElement( ColorPalette, {
						value: attrs.rsmBandBg || '',
						onChange: function ( value ) {
							setAttributes( { rsmBandBg: value || '' } );
						},
						clearable: true,
					} )
				),
			( attrs.rsmBand || '' ) === 'full' &&
				createElement( RangeControl, {
					label: t( 'bandPadTop', 'Band padding top (px)' ),
					value: attrs.rsmBandPadTop || 0,
					onChange: function ( value ) {
						setAttributes( { rsmBandPadTop: value || 0 } );
					},
					min: 0,
					max: 160,
					step: 4,
				} ),
			( attrs.rsmBand || '' ) === 'full' &&
				createElement( RangeControl, {
					label: t( 'bandPadBottom', 'Band padding bottom (px)' ),
					value: attrs.rsmBandPadBottom || 0,
					onChange: function ( value ) {
						setAttributes( { rsmBandPadBottom: value || 0 } );
					},
					min: 0,
					max: 160,
					step: 4,
				} ),
			createElement( RangeControl, {
				label: t( 'spacingTop', 'Space above block (px)' ),
				help: t(
					'spacingHelp',
					'Adds margin above or below this block. Use 0 to butt blocks together.'
				),
				value: attrs.rsmSpacingTop || 0,
				onChange: function ( value ) {
					setAttributes( { rsmSpacingTop: value || 0 } );
				},
				min: 0,
				max: 160,
				step: 4,
			} ),
			createElement( RangeControl, {
				label: t( 'spacingBottom', 'Space below block (px)' ),
				value: attrs.rsmSpacingBottom || 0,
				onChange: function ( value ) {
					setAttributes( { rsmSpacingBottom: value || 0 } );
				},
				min: 0,
				max: 160,
				step: 4,
			} )
		);
	}

	function layoutBandInspectorPanel( jsonContext ) {
		if ( ! jsonContext || ! jsonContext.setAttributes ) {
			return null;
		}

		var attrs = jsonContext.attributes || {};
		var hasLayout =
			( attrs.rsmBand || '' ) === 'full' ||
			!! attrs.rsmBandBg ||
			( attrs.rsmBandPadTop || 0 ) > 0 ||
			( attrs.rsmBandPadBottom || 0 ) > 0 ||
			( attrs.rsmSpacingTop || 0 ) > 0 ||
			( attrs.rsmSpacingBottom || 0 ) > 0;

		return createElement(
			PanelBody,
			{
				title: t( 'blockLayoutBand', 'Layout band' ),
				initialOpen: hasLayout,
			},
			layoutBandFields( jsonContext )
		);
	}

	function blockInspectorSettings( classSuffix, panels, jsonContext ) {
		var inspectorPanels = panels;
		var rsfEditor = window.rsmsparkRsfEditor || {};
		var focusLayoutPanel = !!( rsfEditor.i18n && rsfEditor.i18n.panelLayout );

		if ( jsonContext && ! focusLayoutPanel ) {
			var layoutPanel = layoutBandInspectorPanel( jsonContext );
			if ( layoutPanel ) {
				inspectorPanels = createElement(
					Fragment,
					null,
					layoutPanel,
					panels
				);
			}
		}

		return {
			inspector: createElement( InspectorControls, null, inspectorPanels ),
			canvas: blockCanvasSettingsSection(
				classSuffix,
				panels,
				jsonContext
			),
		};
	}

	function embedWidthInspectorPanel( attributes, setAttributes ) {
		if ( ! PanelBody || ! RangeControl || ! SelectControl || ! attributes || ! setAttributes ) {
			return null;
		}

		var embedWidth = parseInt( attributes.rsmEmbedWidth, 10 );
		if ( isNaN( embedWidth ) || embedWidth <= 0 ) {
			embedWidth = 100;
		}
		embedWidth = Math.max( 40, Math.min( 100, embedWidth ) );
		var embedAlign = attributes.rsmEmbedAlign || 'center';
		var embedPanelChildren = [
			createElement( RangeControl, {
				label: t( 'embedWidth', 'Width (%)' ),
				help: t(
					'embedWidthHelp',
					'Percentage of the available space in this column or content area, not the full browser width. 100% fills the column. On small screens, width is always 100%.'
				),
				value: embedWidth,
				onChange: function ( value ) {
					setAttributes( { rsmEmbedWidth: value || 100 } );
				},
				min: 40,
				max: 100,
				step: 1,
			} ),
		];

		if ( embedWidth < 100 ) {
			embedPanelChildren.push(
				createElement( SelectControl, {
					label: t( 'embedAlign', 'Alignment' ),
					help: t(
						'embedAlignHelp',
						'When width is below 100%, choose how the block sits in the column.'
					),
					value: embedAlign,
					options: [
						{ label: t( 'embedAlignLeft', 'Left' ), value: 'left' },
						{ label: t( 'embedAlignCenter', 'Center' ), value: 'center' },
						{ label: t( 'embedAlignRight', 'Right' ), value: 'right' },
					],
					onChange: function ( value ) {
						setAttributes( { rsmEmbedAlign: value || 'center' } );
					},
				} )
			);
		}

		return createElement.apply(
			createElement,
			[
				PanelBody,
				{
					title: t( 'embedWidthPanel', 'Embed width' ),
					initialOpen: true,
				},
			].concat( embedPanelChildren )
		);
	}

	function rsmBlockIcon( iconSlug ) {
		var branding = window.RSMBlockBranding;
		if ( branding && typeof branding.rsmDashiconIcon === 'function' ) {
			return branding.rsmDashiconIcon( iconSlug );
		}

		var slug = String( iconSlug || 'star-filled' )
			.replace( /^dashicons\s+dashicons-/, '' )
			.replace( /^dashicons-/, '' );

		return createElement( 'span', {
			className: 'dashicons dashicons-' + slug + ' rsmspark-cw-block-icon',
			style: {
				color: '#ff6b35',
				width: '24px',
				height: '24px',
				fontSize: '24px',
			},
			'aria-hidden': true,
		} );
	}

	function exampleAttributesForBlock( blockName ) {
		var schema = blockAttributes[ blockName ] || {};
		var attrs = {};

		Object.keys( schema ).forEach( function ( key ) {
			if (
				schema[ key ] &&
				Object.prototype.hasOwnProperty.call( schema[ key ], 'default' )
			) {
				attrs[ key ] = schema[ key ].default;
			}
		} );

		return attrs;
	}

	function blockExampleFor( blockName, blockMeta ) {
		if ( blockMeta && blockMeta.example ) {
			return blockMeta.example;
		}

		return {
			attributes: exampleAttributesForBlock( blockName ),
			viewportWidth: 520,
		};
	}

	var DASHICON_CHOICES = [
		{ label: 'Check', value: 'dashicons dashicons-yes-alt' },
		{ label: 'Star', value: 'dashicons dashicons-star-filled' },
		{ label: 'Performance', value: 'dashicons dashicons-performance' },
		{ label: 'Customizer', value: 'dashicons dashicons-admin-customizer' },
		{ label: 'Art', value: 'dashicons dashicons-art' },
		{ label: 'Text', value: 'dashicons dashicons-editor-textcolor' },
		{ label: 'Heart', value: 'dashicons dashicons-heart' },
		{ label: 'Shield', value: 'dashicons dashicons-shield' },
		{ label: 'Clock', value: 'dashicons dashicons-clock' },
		{ label: 'Groups', value: 'dashicons dashicons-groups' },
		{ label: 'Chart', value: 'dashicons dashicons-chart-bar' },
		{ label: 'Location', value: 'dashicons dashicons-location' },
	];

	function cloneBlockDefault( blockName, attrName ) {
		var schema = blockAttributes[ blockName ];
		if ( ! schema || ! schema[ attrName ] || ! Array.isArray( schema[ attrName ].default ) ) {
			return null;
		}
		return JSON.parse( JSON.stringify( schema[ attrName ].default ) );
	}

	function useSeedRepeaterDefaults( blockName, attrName, attributes, setAttributes ) {
		useEffect(
			function () {
				var current = attributes[ attrName ];
				if ( Array.isArray( current ) && current.length > 0 ) {
					return;
				}
				var defaults = cloneBlockDefault( blockName, attrName );
				if ( ! defaults || ! defaults.length ) {
					return;
				}
				var patch = {};
				patch[ attrName ] = defaults;
				setAttributes( patch );
			},
			[]
		);
	}

	function useSeedScalarDefaults( blockName, attrNames, attributes, setAttributes ) {
		useEffect(
			function () {
				var schema = blockAttributes[ blockName ];
				if ( ! schema ) {
					return;
				}

				var patch = {};
				attrNames.forEach( function ( attrName ) {
					var current = attributes[ attrName ];
					if (
						typeof current === 'string'
							? current.length > 0
							: current !== undefined && current !== null && current !== ''
					) {
						return;
					}
					if (
						! schema[ attrName ]
						|| ! Object.prototype.hasOwnProperty.call(
							schema[ attrName ],
							'default'
						)
					) {
						return;
					}
					patch[ attrName ] = schema[ attrName ].default;
				} );

				if ( Object.keys( patch ).length ) {
					setAttributes( patch );
				}
			},
			[]
		);
	}

	function dashiconPresetValue( iconValue ) {
		var value = String( iconValue || '' );
		for ( var i = 0; i < DASHICON_CHOICES.length; i++ ) {
			if ( DASHICON_CHOICES[ i ].value === value ) {
				return value;
			}
		}
		return value ? '__custom__' : DASHICON_CHOICES[ 0 ].value;
	}

	function DashiconSelectControl( props ) {
		var iconValue = String( props.value || '' );
		var preset = dashiconPresetValue( iconValue );
		var showCustom = preset === '__custom__';
		var previewClass = showCustom && iconValue ? iconValue : preset !== '__custom__' ? preset : '';

		return createElement(
			'div',
			{ className: 'rsmspark-cw-block-editor__icon-field' },
			previewClass
				? createElement( 'span', {
						className: 'rsmspark-cw-block-editor__icon-preview ' + previewClass,
						'aria-hidden': true,
				  } )
				: null,
			createElement( SelectControl, {
				label: props.label || t( 'selectIcon', 'Icon' ),
				help: props.help || t( 'iconHelp', 'Dashicons preset or custom CSS classes (e.g. fa-solid fa-check).' ),
				value: preset,
				options: DASHICON_CHOICES.concat( [
					{ label: t( 'customIcon', 'Custom icon classes' ), value: '__custom__' },
				] ),
				onChange: function ( value ) {
					if ( value === '__custom__' ) {
						props.onChange( iconValue || 'dashicons dashicons-yes-alt' );
						return;
					}
					props.onChange( value );
				},
			} ),
			showCustom
				? createElement( TextControl, {
						label: t( 'customIcon', 'Custom icon classes' ),
						value: iconValue,
						onChange: props.onChange,
				  } )
				: null
		);
	}

	function useDebouncedValue( value, delayMs ) {
		var serialized = JSON.stringify( value );
		var state = useState( value );
		var debounced = state[ 0 ];
		var setDebounced = state[ 1 ];

		useEffect(
			function () {
				var timer = setTimeout( function () {
					setDebounced( value );
				}, delayMs );

				return function () {
					clearTimeout( timer );
				};
			},
			[ serialized, delayMs ]
		);

		return debounced;
	}

	var FEATURE_CHIP_KEYS = [
		'enabled',
		'title',
		'description',
		'title_icon',
		'title_image',
		'accent_color',
		'icon_style',
		'icon_color',
		'icon_badge_circle_color',
		'icon_badge_text_color',
		'icon_badge_glyph',
		'chip_shape',
		'popout_hot',
		'icon_align',
		'icon_size',
		'icon_size_custom',
	];

	function sanitizeFeatureChipForBlock( feature ) {
		if ( ! feature || typeof feature !== 'object' ) {
			return feature;
		}

		var next = {};
		FEATURE_CHIP_KEYS.forEach( function ( key ) {
			if ( Object.prototype.hasOwnProperty.call( feature, key ) ) {
				next[ key ] = feature[ key ];
			}
		} );

		if ( Object.prototype.hasOwnProperty.call( feature, 'enabled' ) ) {
			next.enabled = soTruthy( feature.enabled );
		}
		if ( Object.prototype.hasOwnProperty.call( feature, 'popout_hot' ) ) {
			next.popout_hot = soTruthy( feature.popout_hot );
		}
		if ( Object.prototype.hasOwnProperty.call( next, 'title_image' ) ) {
			next.title_image = parseInt( next.title_image, 10 ) || 0;
		}

		return next;
	}

	function attributesForServerPreview( blockName, attributes ) {
		var schema = blockAttributes[ blockName ] || {};
		var source = attributes || {};
		var out = Object.assign( {}, source );

		Object.keys( schema ).forEach( function ( key ) {
			if ( ! Object.prototype.hasOwnProperty.call( source, key ) ) {
				return;
			}

			var value = source[ key ];
			if ( value === undefined || value === null ) {
				return;
			}

			var type = schema[ key ] && schema[ key ].type;
			if ( type === 'number' ) {
				out[ key ] = parseInt( value, 10 );
				if ( isNaN( out[ key ] ) ) {
					out[ key ] = schema[ key ].default || 0;
				}
				return;
			}
			if ( type === 'boolean' ) {
				out[ key ] = soTruthy( value );
				return;
			}
			if ( type === 'array' ) {
				out[ key ] = Array.isArray( value ) ? value.slice() : [];
				return;
			}

			out[ key ] = value;
		} );

		if (
			blockName === 'rsm-content-widgets/features' &&
			Array.isArray( out.features )
		) {
			out.features = out.features.map( sanitizeFeatureChipForBlock );
		}

		if ( blockName === 'rsm-content-widgets/headline' ) {
			[ 'overline', 'headline', 'subheadline', 'headline_tag' ].forEach(
				function ( key ) {
					if (
						out[ key ] !== undefined
						&& out[ key ] !== null
						&& '' !== out[ key ]
					) {
						return;
					}
					if (
						schema[ key ]
						&& Object.prototype.hasOwnProperty.call(
							schema[ key ],
							'default'
						)
					) {
						out[ key ] = schema[ key ].default;
					}
				}
			);
		}

		return out;
	}

	function initPreviewWidgets( canvas ) {
		if ( ! canvas ) {
			return;
		}

		if (
			typeof window.rsmContentWidgetsInit === 'function'
		) {
			window.rsmContentWidgetsInit( canvas );
		}

		if ( typeof window.rsmFeaturesInit === 'function' ) {
			window.rsmFeaturesInit( canvas );
		}
	}

	function usePreviewWidgetInit( canvasRef, debouncedAttributes, blockName ) {
		useEffect(
			function () {
				var canvas = canvasRef.current;
				if ( ! canvas ) {
					return undefined;
				}

				var initTimer = null;

				function scheduleInit() {
					window.clearTimeout( initTimer );
					initTimer = window.setTimeout( function () {
						initPreviewWidgets( canvas );
					}, 50 );
				}

				scheduleInit();

				var observer = new window.MutationObserver( scheduleInit );
				observer.observe( canvas, {
					childList: true,
					subtree: true,
				} );

				return function () {
					window.clearTimeout( initTimer );
					observer.disconnect();
				};
			},
			[ debouncedAttributes, blockName ]
		);
	}

	function BlockServerPreview( props ) {
		var canvasRef = useRef();
		var compact = !! props.compact;
		var debounceMs = compact
			? 0
			: parseInt( config.previewDebounceMs, 10 ) || 450;
		var debouncedAttributes = useDebouncedValue(
			attributesForServerPreview(
				props.blockName,
				props.attributes || {}
			),
			debounceMs
		);

		usePreviewWidgetInit(
			canvasRef,
			debouncedAttributes,
			props.blockName
		);

		if ( ! ServerSideRender || ! props.blockName ) {
			return createElement(
				'div',
				{
					className:
						'rsmspark-cw-block-editor__preview-wrap rsmspark-cw-block-editor__preview-notice',
				},
				compact
					? null
					: createElement(
							'p',
							{ className: 'rsmspark-cw-block-editor__heading' },
							t( 'preview', 'Preview' )
					  ),
				createElement(
					'p',
					null,
					t(
						'previewUnavailable',
						'Live preview is unavailable in this editor.'
					)
				)
			);
		}

		var wrapClass =
			'rsmspark-cw-block-editor__preview-wrap rsmspark-cw-block-editor__preview-live' +
			( compact ? ' rsmspark-cw-block-editor__preview-live--compact' : '' );

		return createElement(
			'div',
			{
				className: wrapClass,
			},
			compact
				? null
				: createElement(
						'p',
						{ className: 'rsmspark-cw-block-editor__heading' },
						t( 'preview', 'Preview' )
				  ),
			createElement(
				'div',
				{
					ref: canvasRef,
					className:
						'rsmspark-cw-block-editor__preview-canvas' +
						( compact
							? ' rsmspark-cw-block-editor__preview-canvas--compact'
							: '' ),
				},
				createElement( ServerSideRender, {
					block: props.blockName,
					attributes: debouncedAttributes,
					httpMethod: 'POST',
					urlQueryArgs: compact
						? { rsm_inserter_preview: '1' }
						: undefined,
					LoadingResponsePlaceholder: function () {
						return createElement(
							'div',
							{
								className:
									'rsmspark-cw-block-editor__preview-loading',
							},
							createElement( Spinner, null ),
							createElement(
								'span',
								null,
								t( 'previewLoading', 'Loading preview…' )
							)
						);
					},
					ErrorResponsePlaceholder: function ( errorProps ) {
						var raw =
							( errorProps && errorProps.error ) ||
							( errorProps && errorProps.response );
						var message =
							typeof raw === 'string'
								? raw
								: raw && raw.message
									? raw.message
									: t(
											'previewError',
											'Preview could not be loaded.'
									  );
						return createElement(
							'div',
							{
								className:
									'rsmspark-cw-block-editor__preview-error',
							},
							createElement( 'p', null, String( message ) ),
							createElement(
								'p',
								{ className: 'description' },
								t(
									'previewFrontEndHint',
									'Save and use View / Preview on the front end if this looks wrong.'
								)
							)
						);
					},
					EmptyResponsePlaceholder: function () {
						return createElement(
							'div',
							{
								className:
									'rsmspark-cw-block-editor__preview-empty',
							},
							createElement(
								'p',
								null,
								t(
									'previewEmpty',
									'No preview output. Check widget settings or enable the widget under Content Widgets → Widgets.'
								)
							)
						);
					},
				} )
			),
			compact
				? null
				: createElement(
						'p',
						{
							className:
								'rsmspark-cw-block-editor__preview-hint description',
						},
						t(
							'previewFrontEndHint',
							'Save and use View / Preview on the front end if this looks wrong.'
						)
				  )
		);
	}

	function blockLivePreview( blockName, attributes, compact ) {
		return createElement( BlockServerPreview, {
			blockName: blockName,
			attributes: attributes,
			compact: !! compact,
		} );
	}

	function BlockEditLayout( props ) {
		var previewState = useState( true );
		var isPreviewOnly = previewState[ 0 ];
		var setPreviewOnly = previewState[ 1 ];
		var clientId = props.clientId;
		var blockName = props.blockName;
		var attributes = props.attributes;
		var previewDisabled = props.previewDisabled;
		var previewDisabledMessage = props.previewDisabledMessage;
		var inserterContext =
			typeof useBlockEditContext === 'function'
				? useBlockEditContext()
				: {};

		if (
			blockName &&
			! previewDisabled &&
			isInserterHoverPreview( inserterContext, props )
		) {
			return inserterOutputPreview( blockName, attributes );
		}

		var wrapClass = String( props.className || '' ).trim();
		var blockProps = useBlockProps( {
			className:
				'rsmspark-cw-block-editor-wrap' +
				( wrapClass ? ' ' + wrapClass : '' ) +
				( isPreviewOnly ? ' rsmspark-cw-block-editor-wrap--preview-only' : '' ),
		} );

		var isSelected = useSelect(
			function ( select ) {
				if ( ! clientId ) {
					return true;
				}

				try {
					return !! select( 'core/block-editor' ).isBlockSelected(
						clientId
					);
				} catch ( e ) {
					return true;
				}
			},
			[ clientId ]
		);

		var wasSelectedRef = useRef( isSelected );

		useEffect(
			function () {
				if ( isSelected && ! wasSelectedRef.current ) {
					setPreviewOnly( false );
				}
				if ( ! isSelected && wasSelectedRef.current ) {
					setPreviewOnly( true );
				}
				wasSelectedRef.current = isSelected;
			},
			[ isSelected ]
		);

		function enterPreviewMode( event ) {
			if ( event ) {
				event.stopPropagation();
			}
			flushRichTextInlineEditors();
			setPreviewOnly( true );
		}

		function returnToEditMode( event ) {
			if ( event ) {
				event.stopPropagation();
			}
			setPreviewOnly( false );
		}

		if ( isPreviewOnly && blockName && ! previewDisabled ) {
			return createElement(
				'div',
				Object.assign( {}, blockProps, {
					onClick: returnToEditMode,
					onKeyDown: function ( event ) {
						if ( event.key === 'Enter' || event.key === ' ' ) {
							event.preventDefault();
							returnToEditMode( event );
						}
					},
					role: 'button',
					tabIndex: 0,
					'aria-label': t(
						'returnToBlockSettings',
						'Return to block settings'
					),
				} ),
				createElement(
					'p',
					{ className: 'rsmspark-cw-block-editor__preview-only-hint' },
					t(
						'clickBlockToEdit',
						'Click the block to edit settings.'
					)
				),
				blockLivePreview( blockName, attributes )
			);
		}

		return createElement(
			'div',
			blockProps,
			props.children,
			blockName && ! previewDisabled
				? createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__preview-actions' },
						createElement(
							Button,
							{
								variant: 'primary',
								className: brandButtonClass(
									'primary',
									'rsmspark-cw-block-editor__preview-button'
								),
								onClick: enterPreviewMode,
							},
							t( 'previewBlock', 'Preview' )
						)
				  )
				: previewDisabled
					? createElement(
							'p',
							{
								className:
									'description rsmspark-cw-block-editor__preview-disabled',
							},
							previewDisabledMessage ||
								t(
									'previewUnavailableUntilConfigured',
									'Configure the block above to see a live preview.'
								)
					  )
					: null
		);
	}

	function wrapEditForInserterPreview( blockName, Edit ) {
		if ( ! Edit ) {
			return Edit;
		}

		return function ( props ) {
			var context =
				typeof useBlockEditContext === 'function'
					? useBlockEditContext()
					: {};

			if ( isInserterHoverPreview( context, props ) ) {
				return inserterOutputPreview(
					blockName,
					props.attributes
				);
			}

			return createElement( Edit, props );
		};
	}

	function getTopWindow() {
		try {
			return window.top || window;
		} catch ( err ) {
			return window;
		}
	}

	function getRsmCwMediaPicker() {
		var top = getTopWindow();
		return top.RsmCwMediaPicker || window.RsmCwMediaPicker || null;
	}

	function getRsmMediaOpenFrame() {
		var top = getTopWindow();
		if ( typeof top.rsmMediaOpenFrame === 'function' ) {
			return top.rsmMediaOpenFrame;
		}
		if ( typeof window.rsmMediaOpenFrame === 'function' ) {
			return window.rsmMediaOpenFrame;
		}
		return null;
	}

	function shouldUseRsmCwMedia() {
		if ( ! config.useRsmMedia ) {
			return false;
		}
		var picker = getRsmCwMediaPicker();
		if (
			picker &&
			typeof picker.shouldUseRsmMedia === 'function' &&
			picker.shouldUseRsmMedia()
		) {
			return true;
		}
		return !! getRsmMediaOpenFrame();
	}

	function attachmentThumbUrl( attachment ) {
		var picker = getRsmCwMediaPicker();
		if ( picker && typeof picker.attachmentThumbUrl === 'function' ) {
			return picker.attachmentThumbUrl( attachment );
		}
		if ( ! attachment ) {
			return '';
		}
		if ( attachment.sizes ) {
			if ( attachment.sizes.thumbnail && attachment.sizes.thumbnail.url ) {
				return attachment.sizes.thumbnail.url;
			}
			if ( attachment.sizes.medium && attachment.sizes.medium.url ) {
				return attachment.sizes.medium.url;
			}
		}
		return attachment.url || attachment.icon || '';
	}

	function openRsmCwImagePicker( options, onSelect ) {
		var picker = getRsmCwMediaPicker();
		if ( picker && typeof picker.openImagePicker === 'function' ) {
			picker.openImagePicker( options, onSelect );
			return true;
		}
		var openFrame = getRsmMediaOpenFrame();
		if ( openFrame ) {
			openFrame(
				{
					title: options.title,
					button: { text: options.buttonText },
					library: { type: options.libraryType || 'image' },
					multiple: !! options.multiple,
				},
				onSelect
			);
			return true;
		}
		return false;
	}

	function MediaImageControl( props ) {
		var value = parseInt( props.value, 10 ) || 0;
		var localPreview = useState( props.previewUrl || '' );
		var previewUrl = localPreview[ 0 ] || props.previewUrl || '';
		var useRsmMedia = shouldUseRsmCwMedia();

		wp.element.useEffect(
			function () {
				if ( props.previewUrl ) {
					localPreview[ 1 ]( props.previewUrl );
				}
			},
			[ props.previewUrl ]
		);

		function handleSelect( attachments ) {
			var attachment = attachments && attachments[ 0 ] ? attachments[ 0 ] : null;
			var url = attachmentThumbUrl( attachment );
			localPreview[ 1 ]( url );
			props.onChange( {
				id: attachment && attachment.id ? String( attachment.id ) : '',
				url: url,
			} );
		}

		function openPicker() {
			if (
				openRsmCwImagePicker(
					{
						title: props.label || t( 'selectImage', 'Select image' ),
						buttonText: previewUrl
							? t( 'replaceImage', 'Replace image' )
							: t( 'selectImage', 'Select image' ),
					},
					handleSelect
				)
			) {
				return;
			}

			var mediaWindow = getTopWindow();
			if (
				typeof mediaWindow.wp !== 'undefined' &&
				mediaWindow.wp.media
			) {
				var frame = mediaWindow.wp.media( {
					title: props.label || t( 'selectImage', 'Select image' ),
					button: {
						text: previewUrl
							? t( 'replaceImage', 'Replace image' )
							: t( 'selectImage', 'Select image' ),
					},
					library: { type: 'image' },
					multiple: false,
				} );
				frame.on( 'select', function () {
					var model = frame.state().get( 'selection' ).first();
					handleSelect( model ? [ model.toJSON() ] : [] );
				} );
				frame.open();
			}
		}

		if ( useRsmMedia ) {
			return createElement(
				BaseControl,
				{ label: props.label, help: props.help || '' },
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor__media' },
					previewUrl
						? createElement( 'img', {
								className: 'rsmspark-cw-block-editor__media-preview',
								src: previewUrl,
								alt: '',
						  } )
						: null,
					createElement(
						Button,
						{
							variant: 'primary',
							className: brandButtonClass(
								'primary',
								'rsmspark-cw-block-editor__media-button'
							),
							onClick: openPicker,
						},
						previewUrl
							? t( 'replaceImage', 'Replace image' )
							: t( 'selectImage', 'Select image' )
					),
					previewUrl
						? createElement(
								Button,
								{
									variant: 'link',
									isDestructive: true,
									onClick: function () {
										localPreview[ 1 ]( '' );
										props.onChange( { id: '', url: '' } );
									},
								},
								t( 'removeImage', 'Remove image' )
						  )
						: null
				)
			);
		}

		return createElement(
			BaseControl,
			{ label: props.label, help: props.help || '' },
			createElement(
				'div',
				{ className: 'rsmspark-cw-block-editor__media' },
				previewUrl
					? createElement( 'img', {
							className: 'rsmspark-cw-block-editor__media-preview',
							src: previewUrl,
							alt: '',
					  } )
					: null,
				createElement(
					MediaUploadCheck,
					null,
					createElement( MediaUpload, {
						onSelect: function ( media ) {
							localPreview[ 1 ]( media && media.url ? media.url : '' );
							props.onChange( {
								id: media && media.id ? String( media.id ) : '',
								url: media && media.url ? media.url : '',
							} );
						},
						allowedTypes: [ 'image' ],
						value: value || undefined,
						render: function ( renderProps ) {
							return createElement(
								Fragment,
								null,
								createElement(
									Button,
									{
										variant: 'primary',
										className: brandButtonClass(
											'primary',
											'rsmspark-cw-block-editor__media-button'
										),
										onClick: renderProps.open,
									},
									previewUrl
										? t( 'replaceImage', 'Replace image' )
										: t( 'selectImage', 'Select image' )
								),
								previewUrl
									? createElement(
											Button,
											{
												variant: 'link',
												isDestructive: true,
												onClick: function () {
													localPreview[ 1 ]( '' );
													props.onChange( { id: '', url: '' } );
												},
											},
											t( 'removeImage', 'Remove image' )
									  )
									: null
							);
						},
					} )
				)
			)
		);
	}

	function useMediaPreview( attachmentId ) {
		var id = parseInt( attachmentId, 10 ) || 0;
		var state = useState( '' );
		var previewUrl = state[ 0 ];
		var setPreviewUrl = state[ 1 ];

		wp.element.useEffect(
			function () {
				if ( ! id ) {
					setPreviewUrl( '' );
					return;
				}

				if ( apiFetch ) {
					apiFetch( { path: '/wp/v2/media/' + id } )
						.then( function ( media ) {
							var sizes =
								media && media.media_details && media.media_details.sizes
									? media.media_details.sizes
									: {};
							var url =
								( sizes.medium && sizes.medium.source_url ) ||
								( sizes.thumbnail && sizes.thumbnail.source_url ) ||
								( media && media.source_url ) ||
								'';
							setPreviewUrl( url );
						} )
						.catch( function () {
							setPreviewUrl( '' );
						} );
					return;
				}

				if ( wp.media && wp.media.attachment ) {
					var attachment = wp.media.attachment( id );
					attachment.fetch().then( function () {
						var sizes = attachment.get( 'sizes' ) || {};
						var url =
							( sizes.medium && sizes.medium.url ) ||
							( sizes.thumbnail && sizes.thumbnail.url ) ||
							attachment.get( 'url' ) ||
							'';
						setPreviewUrl( url );
					} );
					return;
				}

				setPreviewUrl( '' );
			},
			[ id ]
		);

		return previewUrl;
	}

	function defaultHeroLayer( type ) {
		return {
			type: type || 'image',
			image: 0,
			image_src: '',
			opacity: 100,
			blend_mode: 'normal',
			focal_preset: 'center',
			focal_x: 50,
			focal_y: 50,
		};
	}

	var HERO_PRESET_KEYS = [
		'headline',
		'subheadline',
		'background_image',
		'background_image_src',
		'overlay_opacity',
		'min_height',
		'cta_text',
		'cta_url',
		'layout',
		'split_ratio',
		'image_side',
		'vertical_align',
		'mobile_order',
		'focal_preset',
		'focal_x',
		'focal_y',
		'background_layers',
	];

	function heroPresetChoices() {
		return Array.isArray( config.heroPresets ) ? config.heroPresets : [];
	}

	function heroHasMediaValue( key, value ) {
		if ( key === 'background_image' ) {
			if ( typeof value === 'number' ) {
				return value > 0;
			}
			return String( value || '' ).trim() !== '';
		}
		return String( value || '' ).trim() !== '';
	}

	function heroLayerHasImage( layer ) {
		if ( ! layer || typeof layer !== 'object' ) {
			return false;
		}
		if ( parseInt( layer.image, 10 ) > 0 ) {
			return true;
		}
		return String( layer.image_src || '' ).trim() !== '';
	}

	function mergeHeroPresetConfig( presetConfig, currentAttrs ) {
		var next = Object.assign( {}, currentAttrs || {} );
		var configSlice = Object.assign( {}, presetConfig || {} );
		var key;

		for ( key in configSlice ) {
			if ( Object.prototype.hasOwnProperty.call( configSlice, key ) ) {
				next[ key ] = configSlice[ key ];
			}
		}

		for ( var i = 0; i < HERO_PRESET_KEYS.length; i++ ) {
			key = HERO_PRESET_KEYS[ i ];
			if (
				( key === 'background_image' ||
					key === 'background_image_src' ) &&
				! heroHasMediaValue( key, configSlice[ key ] ) &&
				Object.prototype.hasOwnProperty.call( currentAttrs || {}, key )
			) {
				next[ key ] = currentAttrs[ key ];
			}
		}

		if (
			( ! Array.isArray( configSlice.background_layers ) ||
				! configSlice.background_layers.length ) &&
			Array.isArray( currentAttrs.background_layers ) &&
			currentAttrs.background_layers.length
		) {
			next.background_layers = currentAttrs.background_layers.slice();
		} else if (
			Array.isArray( configSlice.background_layers ) &&
			configSlice.background_layers.length
		) {
			next.background_layers = configSlice.background_layers.map(
				function ( layer, index ) {
					var merged = Object.assign( {}, layer );
					var currentLayer =
						currentAttrs.background_layers &&
						currentAttrs.background_layers[ index ]
							? currentAttrs.background_layers[ index ]
							: null;
					if (
						currentLayer &&
						! heroLayerHasImage( merged ) &&
						String( merged.video_url || '' ).trim() === '' &&
						heroLayerHasImage( currentLayer )
					) {
						merged.image = currentLayer.image || 0;
						merged.image_src = currentLayer.image_src || '';
					}
					return merged;
				}
			);
		}

		return next;
	}

	function pickHeroExportAttributes( attrs ) {
		var picked = {};
		var i;
		for ( i = 0; i < HERO_PRESET_KEYS.length; i++ ) {
			var key = HERO_PRESET_KEYS[ i ];
			if ( Object.prototype.hasOwnProperty.call( attrs, key ) ) {
				picked[ key ] = attrs[ key ];
			}
		}
		return picked;
	}

	function buildHeroExportPayload( attrs, label ) {
		return {
			plugin:
				config.heroExportPlugin || 'rsmspark-content-widgets-hero',
			version: config.version || '',
			exported_at: new Date().toISOString(),
			label: label || '',
			config: pickHeroExportAttributes( attrs ),
		};
	}

	function parseHeroImportPayload( raw ) {
		var payload = raw;
		if ( typeof raw === 'string' ) {
			try {
				payload = JSON.parse( raw );
			} catch ( error ) {
				return null;
			}
		}
		if ( ! payload || typeof payload !== 'object' ) {
			return null;
		}

		if (
			payload.plugin === ( config.heroExportPlugin || 'rsmspark-content-widgets-hero' ) &&
			payload.config &&
			typeof payload.config === 'object'
		) {
			return payload;
		}

		if (
			payload.plugin === 'rsmspark-content-widgets' &&
			payload.block === 'hero' &&
			payload.attributes &&
			typeof payload.attributes === 'object'
		) {
			return {
				plugin: config.heroExportPlugin || 'rsmspark-content-widgets-hero',
				version: payload.version || '',
				exported_at: payload.exported_at || '',
				label: payload.label || '',
				config: pickHeroExportAttributes( payload.attributes ),
			};
		}

		return null;
	}

	var FOOTER_CONFIG_KEYS = [
		'brand',
		'social',
		'columns',
		'copyright',
		'region',
		'show_on',
		'hide_on',
		'show_on_post_types',
		'hide_on_post_types',
		'design',
	];

	function pickFooterExportAttributes( attrs ) {
		var picked = {};
		var i;
		var key;

		for ( i = 0; i < FOOTER_CONFIG_KEYS.length; i++ ) {
			key = FOOTER_CONFIG_KEYS[ i ];
			if ( Object.prototype.hasOwnProperty.call( attrs || {}, key ) ) {
				picked[ key ] = attrs[ key ];
			}
		}

		return picked;
	}

	function buildFooterExportPayload( attrs, label ) {
		return {
			plugin: 'rsmspark-content-widgets',
			widget: 'footer',
			version: config.version || '',
			exported_at: new Date().toISOString(),
			label:
				label ||
				( attrs.brand && attrs.brand.name ) ||
				'Footer',
			config: pickFooterExportAttributes( attrs ),
		};
	}

	function parseFooterImportPayload( raw ) {
		var payload = raw;
		if ( typeof raw === 'string' ) {
			try {
				payload = JSON.parse( raw );
			} catch ( error ) {
				return null;
			}
		}

		if ( ! payload || typeof payload !== 'object' ) {
			return null;
		}

		if (
			payload.plugin === 'rsmspark-content-widgets' &&
			payload.widget === 'footer' &&
			payload.config &&
			typeof payload.config === 'object'
		) {
			return payload;
		}

		if (
			payload.plugin === 'rsmspark-content-widgets' &&
			payload.block === 'footer' &&
			payload.attributes &&
			typeof payload.attributes === 'object'
		) {
			return {
				plugin: 'rsmspark-content-widgets',
				widget: 'footer',
				version: payload.version || '',
				exported_at: payload.exported_at || '',
				label: payload.label || '',
				config: pickFooterExportAttributes( payload.attributes ),
			};
		}

		return null;
	}

	function mergeFooterImportConfig( importConfig, current ) {
		var next = Object.assign( {}, current || {} );
		var key;

		for ( key in importConfig || {} ) {
			if ( Object.prototype.hasOwnProperty.call( importConfig, key ) ) {
				next[ key ] = importConfig[ key ];
			}
		}

		if (
			importConfig &&
			importConfig.brand &&
			! parseInt( importConfig.brand.logo, 10 ) &&
			current &&
			current.brand &&
			parseInt( current.brand.logo, 10 ) > 0
		) {
			next.brand = Object.assign( {}, importConfig.brand, {
				logo: current.brand.logo,
			} );
		}

		if ( importConfig && importConfig.design && current && current.design ) {
			next.design = Object.assign( {}, current.design, importConfig.design );
		}

		return next;
	}

	var FEATURES_BLOCK_KEYS = [
		'title',
		'columns',
		'chip_layout',
		'icon_align',
		'design',
		'enable_popouts',
		'popout_items',
		'features',
	];

	function pickFeaturesBlockAttributes( attrs ) {
		var picked = {};
		var i;
		var key;

		for ( i = 0; i < FEATURES_BLOCK_KEYS.length; i++ ) {
			key = FEATURES_BLOCK_KEYS[ i ];
			if ( Object.prototype.hasOwnProperty.call( attrs || {}, key ) ) {
				picked[ key ] = attrs[ key ];
			}
		}

		return picked;
	}

	function featuresWidgetConfigFromBlockAttrs( attrs ) {
		return nativeFeaturesAttrsToWidgetData( attrs || {}, {} );
	}

	function buildFeaturesExportPayload( attrs, label ) {
		return {
			plugin: 'rsmspark-content-widgets',
			widget: 'features',
			version: config.version || '',
			exported_at: new Date().toISOString(),
			label:
				label ||
				attrs.title ||
				t( 'features', 'Features' ),
			config: featuresWidgetConfigFromBlockAttrs( attrs ),
		};
	}

	function parseJsonImportText( raw ) {
		if ( raw && typeof raw === 'object' ) {
			return raw;
		}

		var text = String( raw || '' ).replace( /^\uFEFF/, '' ).trim();
		if ( ! text ) {
			return null;
		}

		try {
			return JSON.parse( text );
		} catch ( error ) {
			var start = text.indexOf( '{' );
			var end = text.lastIndexOf( '}' );
			if ( start === -1 || end <= start ) {
				return null;
			}
			try {
				return JSON.parse( text.slice( start, end + 1 ) );
			} catch ( error2 ) {
				return null;
			}
		}
	}

	function looksLikeFeaturesSoConfig( value ) {
		return (
			value &&
			typeof value === 'object' &&
			! Array.isArray( value ) &&
			( ( value.general && typeof value.general === 'object' ) ||
				( value.layout && typeof value.layout === 'object' ) ||
				( value.design && typeof value.design === 'object' ) ||
				( value.popouts && typeof value.popouts === 'object' ) )
		);
	}

	function looksLikeFeaturesBlockAttributes( value ) {
		return (
			value &&
			typeof value === 'object' &&
			! Array.isArray( value ) &&
			Array.isArray( value.features )
		);
	}

	function normalizeFeaturesImportConfig( config ) {
		if ( typeof config === 'string' && config.trim() ) {
			config = parseJsonImportText( config );
		}
		if ( ! config || typeof config !== 'object' ) {
			return null;
		}
		return config;
	}

	function buildFeaturesImportResult( source, config ) {
		var normalized = normalizeFeaturesImportConfig( config );
		if ( ! normalized ) {
			return null;
		}

		return {
			plugin: 'rsmspark-content-widgets',
			widget: 'features',
			version: source.version || '',
			exported_at: source.exported_at || '',
			label: source.label || '',
			config: normalized,
			attributes: widgetDataToNativeFeaturesAttrs( normalized ),
		};
	}

	function buildFeaturesImportFromBlockAttributes( source, attributes ) {
		if ( ! attributes || typeof attributes !== 'object' ) {
			return null;
		}

		return {
			plugin: 'rsmspark-content-widgets',
			widget: 'features',
			version: source.version || '',
			exported_at: source.exported_at || '',
			label: source.label || '',
			config: featuresWidgetConfigFromBlockAttrs( attributes ),
			attributes: pickFeaturesBlockAttributes( attributes ),
		};
	}

	function parseFeaturesImportPayload( raw ) {
		var payload = parseJsonImportText( raw );

		if ( ! payload || typeof payload !== 'object' ) {
			return null;
		}

		if (
			payload.widget === 'features' &&
			payload.config &&
			typeof payload.config === 'object'
		) {
			return buildFeaturesImportResult( payload, payload.config );
		}

		if (
			payload.block === 'features' &&
			payload.attributes &&
			typeof payload.attributes === 'object'
		) {
			return buildFeaturesImportFromBlockAttributes(
				payload,
				payload.attributes
			);
		}

		if ( looksLikeFeaturesSoConfig( payload ) ) {
			return buildFeaturesImportResult( payload, payload );
		}

		if ( looksLikeFeaturesBlockAttributes( payload ) ) {
			return buildFeaturesImportFromBlockAttributes( payload, payload );
		}

		return null;
	}

	function mergeFeaturesImportConfig( importAttrs, current ) {
		var next = Object.assign( {}, current || {} );
		var key;
		var i;

		for ( i = 0; i < FEATURES_BLOCK_KEYS.length; i++ ) {
			key = FEATURES_BLOCK_KEYS[ i ];
			if (
				importAttrs &&
				Object.prototype.hasOwnProperty.call( importAttrs, key )
			) {
				next[ key ] = importAttrs[ key ];
			}
		}

		if (
			Array.isArray( importAttrs && importAttrs.features ) &&
			Array.isArray( current && current.features )
		) {
			next.features = importAttrs.features.map( function ( feature, index ) {
				var merged = Object.assign( {}, feature );
				var prev = current.features[ index ];
				if (
					prev &&
					! parseInt( merged.title_image, 10 ) &&
					parseInt( prev.title_image, 10 ) > 0
				) {
					merged.title_image = prev.title_image;
				}
				return merged;
			} );
		}

		if (
			importAttrs &&
			importAttrs.design &&
			typeof importAttrs.design === 'object'
		) {
			next.design = Object.assign(
				{},
				current && current.design,
				importAttrs.design
			);
		}

		return next;
	}

	var blockExportJsonCache = {};

	function copyHeroJson( text ) {
		if ( navigator.clipboard && navigator.clipboard.writeText ) {
			return navigator.clipboard.writeText( text ).catch( function () {
				return Promise.resolve();
			} );
		}
		return Promise.resolve();
	}

	function exportJsonCacheKey( blockName, clientId, scope ) {
		return (
			String( scope || blockName || 'block' ) +
			':' +
			String( clientId || 'default' )
		);
	}

	function rememberBlockExportJson( cacheKey, text ) {
		if ( ! cacheKey ) {
			return;
		}
		blockExportJsonCache[ cacheKey ] = text;
	}

	function showExportJsonPanel( text, options ) {
		options = options || {};
		if (
			( window.location.pathname || '' ).indexOf( 'widgets.php' ) !== -1
		) {
			options.widgetArea = true;
		}

		if (
			window.RsmCwExportJsonUi &&
			typeof window.RsmCwExportJsonUi.show === 'function'
		) {
			window.RsmCwExportJsonUi.show( text, options );
		}
	}

	function exportJsonPreviewElement( exportJson, emptyHelp ) {
		if ( ! String( exportJson || '' ).trim() ) {
			return createElement(
				'p',
				{
					className:
						'description rsmspark-cw-block-editor__export-json-empty',
				},
				emptyHelp
			);
		}

		return createElement(
			Fragment,
			null,
			createElement(
				'p',
				{
					className:
						'description rsmspark-cw-block-editor__export-json-label',
				},
				t(
					'blockConfigExportLabel',
					'Generated export (also copied to your clipboard):'
				)
			),
			createElement(
				'pre',
				{
					className: 'rsmspark-cw-block-editor__export-json-pre',
					tabIndex: 0,
					role: 'textbox',
					'aria-readonly': true,
				},
				exportJson
			)
		);
	}

	function blockSlugFromName( blockName ) {
		return String( blockName || '' ).replace(
			/^rsmspark-content-widgets\//,
			''
		);
	}

	function registrySlugFromBlockName( blockName ) {
		return blockSlugFromName( blockName ).replace( /-/g, '_' );
	}

	function collectBlockKeysFromNested( source, blockName ) {
		var schema = blockAttributes[ blockName ] || {};
		var keys = Object.keys( schema );
		var found = {};

		function walk( value ) {
			var key;

			if ( ! value || typeof value !== 'object' ) {
				return;
			}

			if ( Array.isArray( value ) ) {
				return;
			}

			for ( key in value ) {
				if ( ! Object.prototype.hasOwnProperty.call( value, key ) ) {
					continue;
				}
				if ( keys.indexOf( key ) !== -1 ) {
					found[ key ] = value[ key ];
				}
				if (
					value[ key ] &&
					typeof value[ key ] === 'object' &&
					! Array.isArray( value[ key ] )
				) {
					walk( value[ key ] );
				}
			}
		}

		walk( source );
		return found;
	}

	function looksLikeBlockAttributesPayload( payload, blockName ) {
		var schema = blockAttributes[ blockName ] || {};
		var keys = Object.keys( schema );
		var matches = 0;
		var key;

		if ( ! payload || typeof payload !== 'object' ) {
			return false;
		}

		for ( key in payload ) {
			if (
				Object.prototype.hasOwnProperty.call( payload, key ) &&
				keys.indexOf( key ) !== -1
			) {
				matches += 1;
			}
		}

		return matches >= Math.max( 1, Math.floor( keys.length / 4 ) );
	}

	function mergeGenericImportConfig( importAttrs, current ) {
		return Object.assign( {}, current || {}, importAttrs || {} );
	}

	function buildGenericBlockExportPayload( blockName, attrs ) {
		return {
			plugin: 'rsmspark-content-widgets',
			block: blockSlugFromName( blockName ),
			version: 1,
			exported_at: new Date().toISOString(),
			attributes: attrs,
		};
	}

	function parseGenericBlockImportPayload( raw, blockName ) {
		var payload = parseJsonImportText( raw );

		if ( ! payload || typeof payload !== 'object' ) {
			return null;
		}

		var slug = blockSlugFromName( blockName );

		if ( slug === 'features' ) {
			var featuresParsed = parseFeaturesImportPayload( payload );
			if ( featuresParsed ) {
				return featuresParsed;
			}
		}

		if ( slug === 'footer' ) {
			var footerParsed = parseFooterImportPayload( payload );
			if ( footerParsed ) {
				return footerParsed;
			}
		}

		var registrySlug = registrySlugFromBlockName( blockName );

		if (
			payload.widget === registrySlug &&
			payload.config &&
			typeof payload.config === 'object'
		) {
			return {
				config: payload.config,
				attributes:
					payload.attributes &&
					typeof payload.attributes === 'object'
						? payload.attributes
						: collectBlockKeysFromNested(
								payload.config,
								blockName
						  ),
			};
		}

		if (
			payload.block === slug &&
			payload.attributes &&
			typeof payload.attributes === 'object'
		) {
			return payload.attributes;
		}

		if ( looksLikeBlockAttributesPayload( payload, blockName ) ) {
			return collectBlockKeysFromNested( payload, blockName );
		}

		return null;
	}

	function blockImportAttributesFromParsed( parsed, ctx ) {
		if ( ! parsed || typeof parsed !== 'object' ) {
			return null;
		}

		if ( typeof ctx.mergeImport === 'function' ) {
			return ctx.mergeImport( parsed, ctx.attributes || {} );
		}

		if ( parsed.attributes && typeof parsed.attributes === 'object' ) {
			if ( typeof ctx.mergeImport === 'function' ) {
				return ctx.mergeImport( parsed, ctx.attributes || {} );
			}
			return mergeGenericImportConfig(
				parsed.attributes,
				ctx.attributes || {}
			);
		}

		if ( parsed.config && typeof parsed.config === 'object' ) {
			var slug = blockSlugFromName( ctx.blockName );
			if ( slug === 'features' ) {
				return mergeFeaturesImportConfig(
					widgetDataToNativeFeaturesAttrs( parsed.config ),
					ctx.attributes || {}
				);
			}
			if ( slug === 'footer' ) {
				return mergeFooterImportConfig(
					parsed.config,
					ctx.attributes || {}
				);
			}

			return mergeGenericImportConfig(
				collectBlockKeysFromNested( parsed.config, ctx.blockName ),
				ctx.attributes || {}
			);
		}

		if (
			! parsed.plugin &&
			! parsed.widget &&
			! parsed.block &&
			! parsed.config
		) {
			return parsed;
		}

		return null;
	}

	function BlockLayoutBandControls( props ) {
		var ctx = props.jsonContext || {};
		var fields = layoutBandFields( ctx );

		if ( ! fields ) {
			return null;
		}

		return createElement(
			'div',
			{ className: 'rsmspark-cw-block-editor__layout-band' },
			createElement(
				'h3',
				{ className: 'rsmspark-cw-block-editor__layout-band-title' },
				t( 'blockLayoutBand', 'Layout band' )
			),
			fields
		);
	}

	function BlockConfigJsonControls( props ) {
		var ctx = props.jsonContext || {};
		var exportCacheKey = exportJsonCacheKey(
			ctx.blockName,
			ctx.clientId,
			'block-json'
		);
		var exportEmptyHelp = t(
			'blockConfigExportHelp',
			'Click Export JSON to generate the current block settings.'
		);
		var importState = useState( '' );
		var importJson = importState[ 0 ];
		var setImportJson = importState[ 1 ];
		var exportState = useState(
			blockExportJsonCache[ exportCacheKey ] || ''
		);
		var exportJson = exportState[ 0 ];
		var setExportJson = exportState[ 1 ];
		var importErrorState = useState( '' );
		var importError = importErrorState[ 0 ];
		var setImportError = importErrorState[ 1 ];
		var statusState = useState( '' );
		var statusMessage = statusState[ 0 ];
		var setStatusMessage = statusState[ 1 ];

		function buildExportPayload() {
			if ( typeof ctx.buildExport === 'function' ) {
				return ctx.buildExport( ctx.attributes || {} );
			}

			return buildGenericBlockExportPayload(
				ctx.blockName,
				ctx.attributes || {}
			);
		}

		function parseImportPayload( raw ) {
			if ( typeof ctx.parseImport === 'function' ) {
				return ctx.parseImport( raw );
			}

			return parseGenericBlockImportPayload( raw, ctx.blockName );
		}

		function exportBlockJson( event ) {
			var payload;
			var text;

			if ( event && event.preventDefault ) {
				event.preventDefault();
			}
			if ( event && event.stopPropagation ) {
				event.stopPropagation();
			}

			try {
				payload = buildExportPayload();
			} catch ( error ) {
				setImportError(
					t(
						'blockConfigImportError',
						'Paste valid RSM block JSON export data for this block type.'
					)
				);
				setStatusMessage( '' );
				return;
			}

			if ( ! payload || typeof payload !== 'object' ) {
				setImportError(
					t(
						'blockConfigImportError',
						'Paste valid RSM block JSON export data for this block type.'
					)
				);
				setStatusMessage( '' );
				return;
			}

			text = JSON.stringify( payload, null, 2 );
			rememberBlockExportJson( exportCacheKey, text );
			setExportJson( text );
			setImportError( '' );
			setStatusMessage(
				t(
					'blockConfigExportReady',
					'Export generated below. Copy the JSON or save it as a file.'
				)
			);
			copyHeroJson( text );
			showExportJsonPanel( text, {
				title: t( 'blockConfigExportPanelTitle', 'Block JSON export ready' ),
			} );
		}

		function applyBlockImport() {
			var parsed = parseImportPayload( importJson );
			if ( ! parsed ) {
				setImportError(
					t(
						'blockConfigImportError',
						'Paste valid RSM block JSON export data for this block type.'
					)
				);
				setStatusMessage( '' );
				return;
			}

			setImportError( '' );
			if (
				! window.confirm(
					t(
						'blockConfigImportConfirm',
						'Apply this JSON import? Current block settings will be overwritten.'
					)
				)
			) {
				return;
			}

			var next = blockImportAttributesFromParsed( parsed, ctx );
			if ( ! next || typeof next !== 'object' ) {
				setImportError(
					t(
						'blockConfigImportError',
						'Paste valid RSM block JSON export data for this block type.'
					)
				);
				setStatusMessage( '' );
				return;
			}

			ctx.setAttributes( next );

			setStatusMessage(
				t( 'blockConfigImportApplied', 'Import applied to this block.' )
			);
		}

		return createElement(
			'div',
			{ className: 'rsmspark-cw-block-editor__json-shell' },
			createElement(
				'p',
				{ className: 'rsmspark-cw-block-editor__heading' },
				t( 'blockConfigJson', 'Import / export JSON' )
			),
			createElement(
				'p',
				{ className: 'components-base-control__help' },
				t(
					'blockConfigJsonHelp',
					'Export or import this block as JSON. Use Show block settings to edit layout fields in the canvas.'
				)
			),
			createElement(
				'div',
				{ className: 'rsmspark-cw-block-editor__json-actions' },
				createElement(
					Button,
					{
						variant: 'secondary',
						className: brandButtonClass( 'secondary' ),
						onClick: exportBlockJson,
					},
					t( 'blockConfigExport', 'Export JSON' )
				),
				createElement(
					Button,
					{
						variant: 'primary',
						className: brandButtonClass( 'primary' ),
						onClick: applyBlockImport,
						disabled: ! String( importJson || '' ).trim(),
					},
					t( 'blockConfigImportApply', 'Apply import' )
				)
			),
			exportJsonPreviewElement( exportJson, exportEmptyHelp ),
			statusMessage
				? createElement(
						Notice,
						{
							status: 'success',
							isDismissible: true,
							onRemove: function () {
								setStatusMessage( '' );
							},
						},
						statusMessage
				  )
				: null,
			importError
				? createElement(
						Notice,
						{
							status: 'error',
							isDismissible: true,
							onRemove: function () {
								setImportError( '' );
							},
						},
						importError
				  )
				: null,
			createElement( TextareaControl, {
				label: t( 'blockConfigImport', 'Import JSON' ),
				help: t(
					'blockConfigImportHelp',
					'Paste exported JSON from another block of the same type.'
				),
				value: importJson,
				onChange: function ( value ) {
					setImportJson( value );
					setImportError( '' );
				},
				rows: 5,
			} )
		);
	}

	function HeroPresetsControls( props ) {
		var attrs = props.attributes;
		var setAttributes = props.setAttributes;
		var exportCacheKey = exportJsonCacheKey(
			'rsm-content-widgets/hero',
			props.clientId,
			'hero-presets'
		);
		var exportEmptyHelp = t(
			'heroPresetExportHelp',
			'Click Export JSON to generate the current hero settings.'
		);
		var presetState = useState( '' );
		var presetSlug = presetState[ 0 ];
		var setPresetSlug = presetState[ 1 ];
		var importState = useState( '' );
		var importJson = importState[ 0 ];
		var setImportJson = importState[ 1 ];
		var exportState = useState(
			blockExportJsonCache[ exportCacheKey ] || ''
		);
		var exportJson = exportState[ 0 ];
		var setExportJson = exportState[ 1 ];
		var importErrorState = useState( '' );
		var importError = importErrorState[ 0 ];
		var setImportError = importErrorState[ 1 ];
		var statusState = useState( '' );
		var statusMessage = statusState[ 0 ];
		var setStatusMessage = statusState[ 1 ];
		var presetOptions = [ { label: '—', value: '' } ].concat(
			heroPresetChoices().map( function ( item ) {
				return {
					label: item.label,
					value: item.slug,
				};
			} )
		);

		function applyHeroPreset( slug ) {
			var preset = heroPresetChoices().find( function ( item ) {
				return item.slug === slug;
			} );
			if ( ! preset ) {
				return;
			}
			if (
				! window.confirm(
					t(
						'heroPresetConfirm',
						'Apply this hero preset? Layout and appearance values will be overwritten.'
					)
				)
			) {
				return;
			}
			setAttributes(
				mergeHeroPresetConfig( preset.config || {}, attrs )
			);
			setStatusMessage(
				t( 'heroPresetApplied', 'Preset applied. Add or keep your background image as needed.' )
			);
		}

		function applyHeroImport() {
			var payload = parseHeroImportPayload( importJson );
			if ( ! payload ) {
				setImportError(
					t(
						'heroPresetImportError',
						'Paste valid RSM Hero JSON export data.'
					)
				);
				setStatusMessage( '' );
				return;
			}
			setImportError( '' );
			if (
				! window.confirm(
					t(
						'heroPresetConfirm',
						'Apply this hero preset? Layout and appearance values will be overwritten.'
					)
				)
			) {
				return;
			}
			setAttributes(
				mergeHeroPresetConfig( payload.config || {}, attrs )
			);
			setStatusMessage(
				t( 'heroPresetImportApplied', 'Import applied to this hero block.' )
			);
		}

		function exportHeroJson( event ) {
			var payload = buildHeroExportPayload(
				attrs,
				attrs.headline || 'Hero'
			);
			var text = JSON.stringify( payload, null, 2 );

			if ( event && event.preventDefault ) {
				event.preventDefault();
			}
			if ( event && event.stopPropagation ) {
				event.stopPropagation();
			}

			rememberBlockExportJson( exportCacheKey, text );
			setExportJson( text );
			setImportError( '' );
			setStatusMessage(
				t(
					'heroPresetExportReady',
					'Export generated below. Copy the JSON or save it as a file.'
				)
			);
			copyHeroJson( text );
			showExportJsonPanel( text, {
				title: t( 'heroPresetExportPanelTitle', 'Hero JSON export ready' ),
			} );
		}

		return createElement(
			'div',
			{ className: 'rsmspark-cw-hero-presets' },
			createElement(
				'p',
				{ className: 'components-base-control__help' },
				t(
					'heroPresetApplyHelp',
					'Overwrites layout and appearance fields. Your background image is kept when the preset does not include one.'
				)
			),
			createElement( SelectControl, {
				label: t( 'heroPresetSelect', 'Layout preset' ),
				value: presetSlug,
				options: presetOptions,
				onChange: function ( value ) {
					setPresetSlug( value );
				},
			} ),
			createElement(
				'div',
				{ className: 'rsmspark-cw-hero-presets__actions' },
				createElement(
					Button,
					{
						variant: 'secondary',
						onClick: function () {
							applyHeroPreset( presetSlug );
						},
						disabled: ! presetSlug,
					},
					t( 'heroPresetApply', 'Apply preset' )
				),
				createElement(
					Button,
					{
						variant: 'secondary',
						onClick: exportHeroJson,
					},
					t( 'heroPresetExport', 'Export JSON' )
				),
				createElement(
					Button,
					{
						variant: 'primary',
						onClick: applyHeroImport,
						disabled: ! String( importJson || '' ).trim(),
					},
					t( 'heroPresetImportApply', 'Apply import' )
				)
			),
			statusMessage
				? createElement(
						Notice,
						{ status: 'success', isDismissible: true, onRemove: function () { setStatusMessage( '' ); } },
						statusMessage
				  )
				: null,
			importError
				? createElement(
						Notice,
						{ status: 'error', isDismissible: true, onRemove: function () { setImportError( '' ); } },
						importError
				  )
				: null,
			exportJsonPreviewElement( exportJson, exportEmptyHelp ),
			createElement( TextareaControl, {
				label: t( 'heroPresetImport', 'Import JSON' ),
				help: t(
					'heroPresetImportHelp',
					'Paste an RSM Hero export, then click Apply import.'
				),
				value: importJson,
				onChange: function ( value ) {
					setImportJson( value );
					setImportError( '' );
				},
				rows: 6,
			} )
		);
	}

	var HERO_BLEND_OPTIONS = [
		{ label: 'Normal', value: 'normal' },
		{ label: 'Multiply', value: 'multiply' },
		{ label: 'Screen', value: 'screen' },
		{ label: 'Overlay', value: 'overlay' },
		{ label: 'Darken', value: 'darken' },
		{ label: 'Lighten', value: 'lighten' },
		{ label: 'Soft light', value: 'soft-light' },
		{ label: 'Hard light', value: 'hard-light' },
	];

	function HeroLayerEditor( props ) {
		var item = props.item || defaultHeroLayer();
		var imagePreview = useMediaPreview( item.image );
		var thumbPreview = item.image_src || imagePreview;

		return createElement(
			'div',
			{ className: 'rsmspark-cw-repeater-item', key: 'hero-layer-' + props.index },
			createElement(
				'p',
				{ className: 'rsmspark-cw-repeater-item__title' },
				t( 'heroLayer', 'Layer' ) + ' ' + ( props.index + 1 )
			),
			createElement( MediaImageControl, {
				label: t( 'heroLayerImageField', 'Layer image' ),
				value: item.image,
				previewUrl: thumbPreview,
				onChange: function ( media ) {
					props.onMediaChange( media );
				},
			} ),
			createElement( RangeControl, {
				label: t( 'heroLayerOpacity', 'Opacity' ),
				value: item.opacity != null ? item.opacity : 100,
				onChange: function ( value ) {
					props.onChange( 'opacity', value );
				},
				min: 0,
				max: 100,
			} ),
			createElement( SelectControl, {
				label: t( 'heroLayerBlend', 'Blend mode' ),
				value: item.blend_mode || 'normal',
				options: HERO_BLEND_OPTIONS,
				onChange: function ( value ) {
					props.onChange( 'blend_mode', value );
				},
			} ),
			createElement( SelectControl, {
				label: t( 'heroLayerFocal', 'Focal point' ),
				value: item.focal_preset || 'center',
				options: [
					{ label: 'Center', value: 'center' },
					{ label: 'Top', value: 'top' },
					{ label: 'Bottom', value: 'bottom' },
					{ label: 'Left', value: 'left' },
					{ label: 'Right', value: 'right' },
					{ label: 'Custom', value: 'custom' },
				],
				onChange: function ( value ) {
					props.onChange( 'focal_preset', value );
				},
			} ),
			createElement(
				Button,
				{ variant: 'secondary', isDestructive: true, onClick: props.onRemove },
				t( 'removeItem', 'Remove' )
			)
		);
	}

	function HeroEdit( props ) {
		var attrs = props.attributes;
		var bgPreview = useMediaPreview( attrs.background_image );
		var layers = Array.isArray( attrs.background_layers )
			? attrs.background_layers.slice()
			: [];
		var heroPresetsProps = {
			attributes: attrs,
			setAttributes: props.setAttributes,
			clientId: props.clientId,
		};

		function updateLayer( index, field, value ) {
			var next = layers.slice();
			next[ index ] = Object.assign( {}, next[ index ] || defaultHeroLayer(), {} );
			next[ index ][ field ] = value;
			props.setAttributes( { background_layers: next } );
		}

		function updateLayerMedia( index, media ) {
			var next = layers.slice();
			next[ index ] = Object.assign( {}, next[ index ] || defaultHeroLayer(), {
				image: parseInt( media && media.id ? media.id : 0, 10 ) || 0,
				image_src: media && media.url ? String( media.url ) : '',
			} );
			props.setAttributes( { background_layers: next } );
		}

		function addLayer() {
			var next = layers.slice();
			if ( ! next.length && ( attrs.background_image || attrs.background_image_src ) ) {
				next.push( {
					type: 'image',
					image: attrs.background_image || 0,
					image_src: attrs.background_image_src || '',
							opacity: 100,
					blend_mode: 'normal',
					focal_preset: attrs.focal_preset || 'center',
					focal_x: attrs.focal_x != null ? attrs.focal_x : 50,
					focal_y: attrs.focal_y != null ? attrs.focal_y : 50,
				} );
			} else {
				next.push( defaultHeroLayer( 'image' ) );
			}
			props.setAttributes( { background_layers: next } );
		}

		function removeLayer( index ) {
			props.setAttributes( {
				background_layers: layers.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		var heroLayoutPanels = createElement(
			Fragment,
			null,
			createElement(
				PanelBody,
				{ title: t( 'layout', 'Layout' ), initialOpen: false },
					createElement( SelectControl, {
						label: t( 'layout', 'Layout' ),
						value: attrs.layout || 'centered',
						options: [
							{ label: 'Centered overlay', value: 'centered' },
							{ label: 'Split (text + image)', value: 'split' },
						],
						onChange: function ( value ) {
							props.setAttributes( { layout: value } );
						},
					} ),
					attrs.layout === 'split'
						? createElement(
							Fragment,
							null,
							createElement( SelectControl, {
								label: 'Split ratio',
								value: attrs.split_ratio || '50-50',
								options: [
									{ label: '50 / 50', value: '50-50' },
									{ label: '40 / 60', value: '40-60' },
									{ label: '60 / 40', value: '60-40' },
								],
								onChange: function ( value ) {
									props.setAttributes( { split_ratio: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Image side',
								value: attrs.image_side || 'right',
								options: [
									{ label: 'Image left', value: 'left' },
									{ label: 'Image right', value: 'right' },
								],
								onChange: function ( value ) {
									props.setAttributes( { image_side: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Content vertical align',
								value: attrs.vertical_align || 'center',
								options: [
									{ label: 'Top', value: 'top' },
									{ label: 'Center', value: 'center' },
									{ label: 'Bottom', value: 'bottom' },
								],
								onChange: function ( value ) {
									props.setAttributes( { vertical_align: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Mobile stack order',
								value: attrs.mobile_order || 'content-first',
								options: [
									{ label: 'Content first', value: 'content-first' },
									{ label: 'Image first', value: 'image-first' },
								],
								onChange: function ( value ) {
									props.setAttributes( { mobile_order: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Image focal point',
								value: attrs.focal_preset || 'center',
								options: [
									{ label: 'Center', value: 'center' },
									{ label: 'Top', value: 'top' },
									{ label: 'Bottom', value: 'bottom' },
									{ label: 'Left', value: 'left' },
									{ label: 'Right', value: 'right' },
									{ label: 'Custom', value: 'custom' },
								],
								onChange: function ( value ) {
									props.setAttributes( { focal_preset: value } );
								},
							} )
						)
						: null
				),
				createElement(
					PanelBody,
					{ title: 'Appearance', initialOpen: false },
					createElement( RangeControl, {
						label: 'Overlay opacity',
						value: attrs.overlay_opacity != null ? attrs.overlay_opacity : 45,
						onChange: function ( value ) {
							props.setAttributes( { overlay_opacity: value } );
						},
						min: 0,
						max: 100,
					} ),
					createElement( RangeControl, {
						label: 'Minimum height (px)',
						value: attrs.min_height != null ? attrs.min_height : 320,
						onChange: function ( value ) {
							props.setAttributes( { min_height: value } );
						},
						min: 200,
						max: 900,
					} ),
					null
				),
				( attrs.layout || 'centered' ) !== 'split'
					? createElement(
							PanelBody,
							{
								title: t( 'heroLayers', 'Background layers' ),
								initialOpen: layers.length > 0,
							},
							createElement(
								'p',
								{ className: 'components-base-control__help' },
								t(
									'heroLayersHelp',
									'Stack images (bottom row first). Leave empty to use the single background image field.'
								)
							),
							layers.map( function ( item, index ) {
								return createElement( HeroLayerEditor, {
									key: 'hero-layer-' + index,
									item: item,
									index: index,
									onChange: function ( field, value ) {
										updateLayer( index, field, value );
									},
									onMediaChange: function ( media ) {
										updateLayerMedia( index, media );
									},
									onRemove: function () {
										removeLayer( index );
									},
								} );
							} ),
							layers.length < 5
								? createElement(
										Button,
										{ variant: 'secondary', onClick: addLayer },
										t( 'addHeroLayer', 'Add layer' )
								  )
								: null
					  )
					: null
		);

		var heroJsonContext = {
			blockName: 'rsm-content-widgets/hero',
			clientId: props.clientId,
			attributes: attrs,
			setAttributes: props.setAttributes,
			buildExport: function ( heroAttrs ) {
				return buildHeroExportPayload(
					heroAttrs,
					heroAttrs.headline || 'Hero'
				);
			},
			parseImport: parseHeroImportPayload,
			mergeImport: function ( payload, current ) {
				return mergeHeroPresetConfig(
					payload.config || {},
					current
				);
			},
		};

		return createElement(
			Fragment,
			null,
			createElement(
				InspectorControls,
				null,
				layoutBandInspectorPanel( heroJsonContext ),
				createElement(
					PanelBody,
					{
						title: t( 'heroPresets', 'Presets' ),
						initialOpen: true,
					},
					createElement( HeroPresetsControls, heroPresetsProps )
				),
				heroLayoutPanels
			),
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/hero',
					attributes: attrs,
				},
				blockCanvasSettingsSection( 'hero', heroLayoutPanels, heroJsonContext ),
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement(
						'p',
						{ className: 'rsmspark-cw-block-editor__heading' },
						t( 'content', 'Content' )
					),
					createElement( TextControl, {
						label: t( 'title', 'Headline' ),
						value: attrs.headline || '',
						onChange: function ( value ) {
							props.setAttributes( { headline: value } );
						},
					} ),
					createElement( TextareaControl, {
						label: 'Subheadline',
						value: attrs.subheadline || '',
						onChange: function ( value ) {
							props.setAttributes( { subheadline: value } );
						},
					} ),
					createElement( MediaImageControl, {
						label: 'Background image',
						help: 'Shown full-width behind the headline on the front end (editor preview is thumbnail-sized only).',
						value: attrs.background_image,
						previewUrl: bgPreview,
						onChange: function ( media ) {
							var id =
								media && media.id ? String( media.id ) : '';
							var url =
								media && media.url ? String( media.url ) : '';
							props.setAttributes( {
								background_image: id,
								background_image_src: url,
							} );
						},
					} ),
					createElement( TextControl, {
						label: 'Button text',
						value: attrs.cta_text || '',
						onChange: function ( value ) {
							props.setAttributes( { cta_text: value } );
						},
					} ),
					createElement( TextControl, {
						label: 'Button URL',
						value: attrs.cta_url || '',
						onChange: function ( value ) {
							props.setAttributes( { cta_url: value } );
						},
					} )
				)
			)
		);
	}

	function RepeaterTextItemsEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		var itemLabel = props.itemLabel || t( 'tab', 'Item' );
		var instanceId = props.instanceId || '0';

		if ( props.blockName ) {
			useSeedRepeaterDefaults(
				props.blockName,
				'items',
				attrs,
				props.setAttributes
			);
		}

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		function addItem() {
			props.setAttributes( {
				items: items.concat( [
					{
						title: itemLabel + ' ' + ( items.length + 1 ),
						content: '',
					},
				] ),
			} );
		}

		function removeItem( index ) {
			props.setAttributes( {
				items: items.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		var configBody = [
			props.settingsBeforeContent || null,
			createElement(
				'div',
				{ className: 'rsmspark-cw-block-editor' },
				createElement(
					'p',
					{ className: 'rsmspark-cw-block-editor__heading' },
					t( 'content', 'Content' )
				),
				items.map( function ( item, index ) {
					return createElement(
						'div',
						{
							key: 'item-' + index,
							className: 'rsmspark-cw-block-editor__repeater-item',
						},
						createElement(
							'div',
							{ className: 'rsmspark-cw-block-editor__repeater-head' },
							createElement(
								'p',
								{ className: 'rsmspark-cw-block-editor__repeater-title' },
								itemLabel + ' ' + ( index + 1 )
							),
							createElement(
								Button,
								{
									variant: 'link',
									isDestructive: true,
									onClick: function () {
										removeItem( index );
									},
									disabled: items.length <= 1,
								},
								t( 'removeItem', 'Remove' )
							)
						),
						createElement( TextControl, {
							label: t( 'title', 'Title' ),
							value: item.title || '',
							onChange: function ( value ) {
								updateItem( index, 'title', value );
							},
						} ),
						createElement( RichTextProfileField, {
							label: t( 'description', 'Content' ),
							profile: 'body',
							fieldKey: 'item-content-' + index,
							instanceId: instanceId,
							value: item.content || '',
							onChange: function ( value ) {
								updateItem( index, 'content', value );
							},
						} )
					);
				} ),
				createElement(
					Button,
					{ variant: 'secondary', onClick: addItem },
					t( 'addItem', 'Add item' )
				)
			)
		];

		if ( props.blockName ) {
			var editContext =
				typeof useBlockEditContext === 'function'
					? useBlockEditContext()
					: {};
			var layoutClientId =
				props.clientId || editContext.clientId || '';

			return createElement(
				BlockEditLayout,
				{
					clientId: layoutClientId,
					blockName: props.blockName,
					attributes: attrs,
				},
				configBody
			);
		}

		return createElement(
			'div',
			useBlockProps( { className: 'rsmspark-cw-block-editor-wrap' } ),
			configBody
		);
	}

	function defaultPopoutItem( chipLabel ) {
		return {
			chip_label: chipLabel || '',
			popout_title: '',
			popout_subtitle: '',
			popout_content: '',
			popout_media: 'inherit',
			popout_icon: '',
			popout_image: 0,
			popout_image_src: '',
		};
	}

	function ensurePopoutRows( items, index, chipTitle ) {
		var next = Array.isArray( items ) ? items.slice() : [];
		while ( next.length <= index ) {
			next.push( defaultPopoutItem( '' ) );
		}
		if ( chipTitle && ! next[ index ].chip_label ) {
			next[ index ] = Object.assign( {}, next[ index ], {
				chip_label: chipTitle,
			} );
		}
		return next;
	}

	function featureChipPlainTitle( title ) {
		return String( title || '' )
			.replace( /<[^>]*>/g, '' )
			.replace( /\s+/g, ' ' )
			.trim();
	}

	function richTextHasContent( html ) {
		return featureChipPlainTitle( html ).length > 0;
	}

	function normalizeFeatureForEditor( feature ) {
		if ( ! feature || typeof feature !== 'object' ) {
			return feature;
		}

		var next = Object.assign( {}, feature );

		if ( ! richTextHasContent( next.description ) && next.body ) {
			next.description = next.body;
		}
		if ( ! richTextHasContent( next.description ) && next.content ) {
			next.description = next.content;
		}

		return next;
	}

	function normalizePopoutItemForEditor( item ) {
		if ( ! item || typeof item !== 'object' ) {
			return item;
		}

		var next = Object.assign( {}, item );

		if ( ! richTextHasContent( next.popout_content ) && next.body ) {
			next.popout_content = next.body;
		}
		if ( ! richTextHasContent( next.popout_content ) && next.content ) {
			next.popout_content = next.content;
		}

		return next;
	}

	function syncPopoutRowsForHotChips( featureList, items ) {
		var next = Array.isArray( items ) ? items.slice() : [];

		if ( ! Array.isArray( featureList ) ) {
			return next;
		}

		featureList.forEach( function ( feature, index ) {
			if ( ! feature || ! soTruthy( feature.popout_hot ) ) {
				return;
			}

			next = ensurePopoutRows(
				next,
				index,
				featureChipPlainTitle( feature.title )
			);
		} );

		return next;
	}

	function useRepeaterAccordionOpenState() {
		var openState = useState( {} );
		var openMap = openState[ 0 ];
		var setOpenMap = openState[ 1 ];

		function isOpen( index ) {
			return !! openMap[ String( index ) ];
		}

		function setOpen( index, value ) {
			setOpenMap( function ( prev ) {
				var next = Object.assign( {}, prev );
				next[ String( index ) ] = !! value;
				return next;
			} );
		}

		function toggleOpen( index ) {
			setOpen( index, ! isOpen( index ) );
		}

		return {
			isOpen: isOpen,
			setOpen: setOpen,
			toggleOpen: toggleOpen,
		};
	}

	function RepeaterPopoutStatusDot( props ) {
		if ( ! props.popoutsEnabled ) {
			return null;
		}

		var className =
			'rsmspark-cw-repeater-accordion__popout-dot' +
			( props.isHot ? ' is-active' : ' is-stub' );

		return createElement( 'span', {
			className: className,
			title: props.isHot
				? t( 'popoutDotActive', 'Hot chip with popout' )
				: t( 'popoutDotStub', 'Popout available — enable Hot to edit' ),
			'aria-hidden': 'true',
		} );
	}

	function RepeaterSubZone( props ) {
		var variant = props.variant || 'hidden';

		if ( variant === 'hidden' ) {
			return null;
		}

		if ( variant === 'stub' ) {
			return createElement(
				'div',
				{
					className:
						'rsmspark-cw-repeater-subzone rsmspark-cw-repeater-subzone--stub',
				},
				createElement(
					'p',
					{ className: 'rsmspark-cw-repeater-subzone__heading' },
					props.title || t( 'popout', 'Popout' )
				),
				createElement(
					'p',
					{ className: 'rsmspark-cw-repeater-subzone__stub-message' },
					props.stubMessage ||
						t(
							'popoutStubMessage',
							'Turn on Hot to edit this chip’s popout content.'
						)
				),
				props.stubAction
			);
		}

		return createElement(
			'div',
			{
				className:
					'rsmspark-cw-repeater-subzone rsmspark-cw-repeater-subzone--active',
			},
			createElement(
				'p',
				{ className: 'rsmspark-cw-repeater-subzone__heading' },
				props.title || t( 'popout', 'Popout' )
			),
			props.children
		);
	}

	function RepeaterAccordionSection( props ) {
		var isOpen = !! props.isOpen;
		var className =
			'rsmspark-cw-repeater-accordion' + ( isOpen ? ' is-open' : '' );

		return createElement(
			'div',
			{ className: className },
			createElement(
				'div',
				{ className: 'rsmspark-cw-repeater-accordion__header' },
				createElement( 'span', {
					className: 'rsmspark-cw-repeater-accordion__handle',
					'aria-hidden': 'true',
					title: t(
						'reorderHandlePlaceholder',
						'Reorder (coming soon)'
					),
				} ),
				createElement(
					'button',
					{
						type: 'button',
						className: 'rsmspark-cw-repeater-accordion__toggle',
						onClick: props.onToggle,
						'aria-expanded': isOpen,
					},
					createElement(
						'span',
						{ className: 'rsmspark-cw-repeater-accordion__toggle-main' },
						props.label,
						createElement(
							'span',
							{ className: 'rsmspark-cw-repeater-accordion__meta' },
							props.meta
						)
					),
					createElement( 'span', {
						className:
							'rsmspark-cw-repeater-accordion__chevron dashicons dashicons-arrow-down-alt2',
						'aria-hidden': 'true',
					} )
				),
				props.headerActions
					? createElement(
							'div',
							{
								className: 'rsmspark-cw-repeater-accordion__actions',
								onClick: function ( event ) {
									event.stopPropagation();
								},
							},
							props.headerActions
					  )
					: null
			),
			isOpen
				? createElement(
						'div',
						{ className: 'rsmspark-cw-repeater-accordion__body' },
						props.children
				  )
				: null
		);
	}

	function repeaterAccordionHeaderLabel( indexLabel, displayTitle ) {
		return createElement(
			Fragment,
			null,
			createElement(
				'span',
				{ className: 'rsmspark-cw-repeater-accordion__index' },
				indexLabel
			),
			createElement(
				'span',
				{ className: 'rsmspark-cw-repeater-accordion__title' },
				displayTitle
			)
		);
	}

	function repeaterAccordionHeaderThumb( previewUrl ) {
		if ( ! previewUrl ) {
			return null;
		}

		return createElement( 'img', {
			className: 'rsmspark-cw-repeater-accordion__icon-img',
			src: previewUrl,
			alt: '',
		} );
	}

	function repeaterAccordionStatusDot( active, title ) {
		if ( ! active ) {
			return null;
		}

		return createElement( 'span', {
			className: 'rsmspark-cw-repeater-accordion__popout-dot is-active',
			title: title || '',
			'aria-hidden': 'true',
		} );
	}

	function GenericRepeaterAccordionEditor( props ) {
		var displayTitle =
			String( props.displayTitle || '' ).trim() || props.fallbackTitle || '';

		return createElement(
			RepeaterAccordionSection,
			{
				isOpen: props.isOpen,
				onToggle: props.onToggle,
				label: repeaterAccordionHeaderLabel(
					props.indexLabel,
					displayTitle
				),
				meta: props.meta || null,
				headerActions: createElement(
					Button,
					{
						variant: 'link',
						isDestructive: true,
						onClick: props.onRemove,
						disabled: props.disableRemove,
					},
					t( 'removeItem', 'Remove' )
				),
			},
			createElement(
				'div',
				{ className: 'rsmspark-cw-repeater-accordion__chip-zone' },
				props.children
			)
		);
	}

	function featureAccordionHeaderIcon( feature, imagePreviewUrl ) {
		if ( parseInt( feature.title_image, 10 ) > 0 && imagePreviewUrl ) {
			return createElement( 'img', {
				className: 'rsmspark-cw-repeater-accordion__icon-img',
				src: imagePreviewUrl,
				alt: '',
			} );
		}

		var iconClass = String( feature.title_icon || '' ).trim();
		if ( iconClass ) {
			return createElement( 'span', {
				className: 'rsmspark-cw-repeater-accordion__icon ' + iconClass,
				'aria-hidden': 'true',
			} );
		}

		return createElement( 'span', {
			className:
				'rsmspark-cw-repeater-accordion__icon rsmspark-cw-repeater-accordion__icon--empty dashicons dashicons-marker',
			'aria-hidden': 'true',
		} );
	}

	function featuresPopoutControls( options ) {
		var popoutsEnabled = !! options.popoutsEnabled;
		var popoutItems = Array.isArray( options.popoutItems )
			? options.popoutItems
			: [];
		var globalsOnly = !! options.globalsOnly;

		var enableControl = createElement( ToggleControl, {
			label: t( 'enablePopouts', 'Enable chip popouts' ),
			help: globalsOnly
				? t(
						'enablePopoutsHelpSidebar',
						'Hot chips open a detail modal. Edit each chip’s popout in the block canvas.'
				  )
				: t(
						'enablePopoutsHelp',
						'Hot chips open a detail modal. Mark chips as Hot to edit popout content under each feature.'
				  ),
			checked: popoutsEnabled,
			onChange: options.onTogglePopouts,
		} );

		if ( globalsOnly ) {
			return enableControl;
		}

		return createElement(
			Fragment,
			null,
			enableControl,
			popoutsEnabled
				? createElement(
						Fragment,
						null,
						popoutItems.map( function ( item, index ) {
							return createElement( FeaturesPopoutItemEditor, {
								key: 'popout-' + index,
								item: item,
								index: index,
								instanceId: options.instanceId,
								onChange: function ( field, value ) {
									options.onChangePopoutItem( index, field, value );
								},
								onMediaChange: function ( media ) {
									options.onChangePopoutMedia( index, media );
								},
								onRemove: function () {
									options.onRemovePopoutItem( index );
								},
							} );
						} ),
						createElement(
							Button,
							{
								variant: 'secondary',
								onClick: options.onAddPopoutItem,
							},
							t( 'addPopout', 'Add popout' )
						)
				  )
				: null
		);
	}

	function FeaturesPopoutItemEditor( props ) {
		var item = props.item;
		var nested = !! props.nested;
		var imagePreview = useMediaPreview( item.popout_image );
		var thumbPreview = item.popout_image_src || imagePreview;
		var wrapperClass = nested
			? 'rsmspark-cw-repeater-subzone__fields'
			: 'rsmspark-cw-block-editor__repeater-item';

		return createElement(
			'div',
			{ className: wrapperClass },
			nested
				? null
				: createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__repeater-head' },
						createElement(
							'p',
							{ className: 'rsmspark-cw-block-editor__repeater-title' },
							t( 'popout', 'Popout' ) + ' ' + ( props.index + 1 )
						),
						createElement(
							Button,
							{
								variant: 'link',
								isDestructive: true,
								onClick: props.onRemove,
							},
							t( 'removeItem', 'Remove' )
						)
				  ),
			nested
				? null
				: createElement( TextControl, {
						label: t( 'chipLabel', 'Chip label' ),
						help: t(
							'chipLabelHelp',
							'Optional. Match the feature title when chip order differs.'
						),
						value: item.chip_label || '',
						onChange: function ( value ) {
							props.onChange( 'chip_label', value );
						},
				  } ),
			createElement( RichTextProfileField, {
				label: t( 'popoutTitle', 'Popout title' ),
				profile: 'headline',
				fieldKey: 'popout-title-' + props.index,
				instanceId: props.instanceId || String( props.index ),
				value: item.popout_title || '',
				help: 'Headline profile. Leave empty to use the chip title.',
				onChange: function ( value ) {
					props.onChange( 'popout_title', value );
				},
			} ),
			createElement( RichTextProfileField, {
				label: t( 'popoutSubtitle', 'Popout sub-headline' ),
				profile: 'headline',
				fieldKey: 'popout-subtitle-' + props.index,
				instanceId: props.instanceId || String( props.index ),
				value: item.popout_subtitle || '',
				onChange: function ( value ) {
					props.onChange( 'popout_subtitle', value );
				},
			} ),
			createElement( SelectControl, {
				label: t( 'popoutMedia', 'Popout icon / image' ),
				value: item.popout_media || 'inherit',
				options: [
					{
						label: t( 'popoutMediaInherit', 'Inherit from feature chip' ),
						value: 'inherit',
					},
					{
						label: t( 'popoutMediaCustom', 'Custom icon or image' ),
						value: 'custom',
					},
				],
				onChange: function ( value ) {
					props.onChange( 'popout_media', value );
				},
			} ),
			( item.popout_media || 'inherit' ) === 'custom'
				? createElement(
						Fragment,
						null,
						createElement( DashiconSelectControl, {
							label: t( 'selectIcon', 'Icon' ),
							value: item.popout_icon || '',
							onChange: function ( value ) {
								props.onChange( 'popout_icon', value );
							},
						} ),
						createElement( MediaImageControl, {
							label: t( 'iconImage', 'Icon image' ),
							value: item.popout_image,
							previewUrl: thumbPreview,
							onChange: function ( media ) {
								props.onMediaChange( media );
							},
						} )
				  )
				: null,
			createElement( RichTextProfileField, {
				label: t( 'popoutContent', 'Popout content' ),
				profile: 'body',
				layout: 'modal',
				fieldKey: 'popout-content-' + props.index,
				instanceId: props.instanceId || String( props.index ),
				value: item.popout_content || '',
				help: 'Body profile: paragraphs, lists, size/color tokens.',
				onChange: function ( value ) {
					props.onChange( 'popout_content', value );
				},
			} )
		);
	}

	function FeatureChipEditor( props ) {
		var feature = props.feature;
		var index = props.index;
		var nested = !! props.nested;
		var preview = useMediaPreview( feature.title_image );
		var wrapperClass = nested
			? 'rsmspark-cw-repeater-accordion__chip-fields'
			: 'rsmspark-cw-block-editor__repeater-item';

		return createElement(
			'div',
			{ className: wrapperClass },
			nested
				? null
				: createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__repeater-head' },
						createElement(
							'p',
							{ className: 'rsmspark-cw-block-editor__repeater-title' },
							t( 'feature', 'Feature' ) + ' ' + ( index + 1 )
						),
						createElement(
							Button,
							{
								variant: 'link',
								isDestructive: true,
								onClick: props.onRemove,
								disabled: props.disableRemove,
							},
							t( 'removeItem', 'Remove' )
						)
				  ),
			createElement( RichTextProfileField, {
				label: t( 'title', 'Title' ),
				profile: 'headline',
				fieldKey: 'feature-title-' + index,
				instanceId: props.instanceId || String( index ),
				value: feature.title || '',
				help: 'Headline profile: size, color, and gradient tokens.',
				onChange: function ( value ) {
					props.onChange( 'title', value );
				},
			} ),
			createElement( RichTextProfileField, {
				label: t( 'description', 'Description' ),
				profile: 'body',
				layout: 'modal',
				fieldKey: 'feature-description-' + props.index,
				instanceId: props.instanceId || String( props.index ),
				value: feature.description || '',
				help: 'Body profile: paragraphs, lists, size/color tokens.',
				onChange: function ( value ) {
					props.onChange( 'description', value );
				},
			} ),
			createElement( DashiconSelectControl, {
				label: t( 'selectIcon', 'Icon' ),
				value: feature.title_icon || '',
				onChange: function ( value ) {
					props.onChange( 'title_icon', value );
				},
			} ),
			createElement( SelectControl, {
				label: t( 'iconStyle', 'Icon style' ),
				value: feature.icon_style || 'glyph',
				options: [
					{ label: t( 'iconStyleGlyph', 'Icon glyph' ), value: 'glyph' },
					{ label: t( 'iconStyleBadge', 'Number in circle' ), value: 'number_badge' },
				],
				onChange: function ( value ) {
					props.onChange( 'icon_style', value );
				},
			} ),
			createElement(
				BaseControl,
				{ label: t( 'iconColor', 'Icon color' ) },
				createElement( ColorPalette, {
					colors: [
						{ name: 'Blue', color: '#2563eb' },
						{ name: 'Orange', color: '#ff6b35' },
						{ name: 'White', color: '#ffffff' },
						{ name: 'Slate', color: '#334155' },
					],
					value: feature.icon_color || '',
					onChange: function ( value ) {
						props.onChange( 'icon_color', value || '' );
					},
				} )
			),
			createElement(
				BaseControl,
				{ label: t( 'badgeCircleColor', 'Badge circle color' ) },
				createElement( ColorPalette, {
					colors: [
						{ name: 'Orange', color: '#ff6b35' },
						{ name: 'Blue', color: '#2563eb' },
						{ name: 'Teal', color: '#0f766e' },
					],
					value: feature.icon_badge_circle_color || feature.accent_color || '#ff6b35',
					onChange: function ( value ) {
						props.onChange( 'icon_badge_circle_color', value || '' );
					},
				} )
			),
			createElement(
				BaseControl,
				{ label: t( 'badgeTextColor', 'Badge number color' ) },
				createElement( ColorPalette, {
					colors: [
						{ name: 'White', color: '#ffffff' },
						{ name: 'Black', color: '#111827' },
					],
					value: feature.icon_badge_text_color || '#ffffff',
					onChange: function ( value ) {
						props.onChange( 'icon_badge_text_color', value || '#ffffff' );
					},
				} )
			),
			createElement( TextControl, {
				label: t( 'badgeGlyph', 'Badge character' ),
				help: t( 'badgeGlyphHelp', 'For Number in circle: enter 0-9 when the icon picker does not provide fa-4.' ),
				value: feature.icon_badge_glyph || '',
				onChange: function ( value ) {
					props.onChange( 'icon_badge_glyph', value );
				},
			} ),
			createElement( MediaImageControl, {
				label: t( 'iconImage', 'Icon image' ),
				help: 'Optional image instead of icon classes.',
				value: feature.title_image,
				previewUrl: preview,
				onChange: function ( media ) {
					props.onChange( 'title_image', parseInt( media.id, 10 ) || 0 );
				},
			} ),
			createElement(
				BaseControl,
				{ label: t( 'accentColor', 'Accent color' ) },
				createElement( ColorPalette, {
					colors: [
						{ name: 'Blue', color: '#2563eb' },
						{ name: 'Teal', color: '#0f766e' },
						{ name: 'Orange', color: '#c2410c' },
						{ name: 'Purple', color: '#7c3aed' },
						{ name: 'Slate', color: '#334155' },
					],
					value: feature.accent_color || '#2563eb',
					onChange: function ( value ) {
						props.onChange( 'accent_color', value || '' );
					},
				} )
			),
			props.popoutsEnabled
				? createElement(
						Fragment,
						null,
						createElement( ToggleControl, {
							label: t( 'popoutHot', 'Hot (opens popout)' ),
							help: t(
								'popoutHotHelp',
								'Opens a detail modal for this chip. Popout fields appear below when enabled.'
							),
							checked: !! feature.popout_hot,
							onChange: function ( value ) {
								props.onChange( 'popout_hot', value );
							},
						} )
				  )
				: null
		);
	}

	function FeatureAccordionEditor( props ) {
		var feature = props.feature;
		var index = props.index;
		var imagePreview = useMediaPreview( feature.title_image );
		var plainTitle = featureChipPlainTitle( feature.title );
		var displayTitle =
			plainTitle ||
			t( 'feature', 'Feature' ) + ' ' + ( index + 1 );
		var isHot = !! feature.popout_hot;
		var popoutItem =
			props.popoutItem || defaultPopoutItem( plainTitle );

		return createElement(
			RepeaterAccordionSection,
			{
				isOpen: props.isOpen,
				onToggle: props.onToggle,
				label: createElement(
					Fragment,
					null,
					createElement(
						'span',
						{ className: 'rsmspark-cw-repeater-accordion__index' },
						t( 'feature', 'Feature' ) + ' ' + ( index + 1 )
					),
					createElement(
						'span',
						{ className: 'rsmspark-cw-repeater-accordion__title' },
						displayTitle
					)
				),
				meta: createElement(
					Fragment,
					null,
					featureAccordionHeaderIcon( feature, imagePreview ),
					createElement( RepeaterPopoutStatusDot, {
						popoutsEnabled: props.popoutsEnabled,
						isHot: isHot,
					} )
				),
				headerActions: createElement(
					Button,
					{
						variant: 'link',
						isDestructive: true,
						onClick: props.onRemove,
						disabled: props.disableRemove,
					},
					t( 'removeItem', 'Remove' )
				),
			},
			createElement(
				'div',
				{ className: 'rsmspark-cw-repeater-accordion__chip-zone' },
				createElement( FeatureChipEditor, {
					nested: true,
					feature: feature,
					index: index,
					instanceId: props.instanceId,
					popoutsEnabled: props.popoutsEnabled,
					onChange: props.onChange,
				} )
			),
			props.popoutsEnabled
				? createElement(
						RepeaterSubZone,
						{
							variant: isHot ? 'active' : 'stub',
							stubMessage: t(
								'popoutStubMessage',
								'Turn on Hot to edit this chip’s popout content.'
							),
							stubAction: createElement(
								Button,
								{
									variant: 'secondary',
									onClick: props.onEnableHot,
								},
								t( 'enablePopoutHot', 'Enable Hot' )
							),
						},
						isHot
							? createElement( FeaturesPopoutItemEditor, {
									nested: true,
									item: popoutItem,
									index: index,
									instanceId: props.instanceId,
									onChange: props.onChangePopoutItem,
									onMediaChange: props.onChangePopoutMedia,
							  } )
							: null
				  )
				: null
		);
	}

	function soTruthy( value ) {
		if ( typeof value === 'boolean' ) {
			return value;
		}
		if ( typeof value === 'string' ) {
			return /^(1|true|yes|on)$/i.test( value );
		}
		return !! value;
	}

	function normalizeSoFeatureForNative( feature ) {
		if ( ! feature || typeof feature !== 'object' ) {
			return feature;
		}

		var next = Object.assign( {}, feature );
		if ( ! next.description && next.body ) {
			next.description = next.body;
		}
		if ( ! next.description && next.content ) {
			next.description = next.content;
		}
		if ( Object.prototype.hasOwnProperty.call( next, 'enabled' ) ) {
			next.enabled = soTruthy( next.enabled );
		}
		if ( Object.prototype.hasOwnProperty.call( next, 'popout_hot' ) ) {
			next.popout_hot = soTruthy( next.popout_hot );
		}
		if ( Object.prototype.hasOwnProperty.call( next, 'title_image' ) ) {
			next.title_image = parseInt( next.title_image, 10 ) || 0;
		}
		return next;
	}

	function soFeatureFromNative( feature ) {
		if ( ! feature || typeof feature !== 'object' ) {
			return feature;
		}

		var next = Object.assign( {}, feature );
		if ( Object.prototype.hasOwnProperty.call( next, 'enabled' ) ) {
			next.enabled = next.enabled ? 'on' : '';
		}
		if ( Object.prototype.hasOwnProperty.call( next, 'popout_hot' ) ) {
			next.popout_hot = next.popout_hot ? 'on' : '';
		}
		return next;
	}

	function widgetDataToNativeFeaturesAttrs( widgetData ) {
		widgetData = widgetData || {};
		var general = widgetData.general || {};
		var layout = widgetData.layout || {};
		var design = widgetData.design || {};
		var popouts = widgetData.popouts || {};
		var features = Array.isArray( general.features ) ? general.features : [];

		return {
			title: general.title || '',
			columns: parseInt( layout.columns, 10 ) || 3,
			chip_layout: layout.chip_layout || 'stacked',
			icon_align: design.icon_align || 'center',
			design: Object.assign( {}, design ),
			enable_popouts: soTruthy( popouts.enable_popouts ),
			popout_items: Array.isArray( popouts.popout_items )
				? popouts.popout_items.slice()
				: [],
			features: features.map( normalizeSoFeatureForNative ),
		};
	}

	function nativeFeaturesAttrsToWidgetData( attrs, existing ) {
		existing = existing || {};
		attrs = attrs || {};

		return Object.assign( {}, existing, {
			general: Object.assign( {}, existing.general || {}, {
				title: attrs.title || '',
				features: Array.isArray( attrs.features )
					? attrs.features.map( soFeatureFromNative )
					: [],
			} ),
			layout: Object.assign( {}, existing.layout || {}, {
				columns: attrs.columns,
				chip_layout: attrs.chip_layout,
			} ),
			design: Object.assign(
				{},
				existing.design || {},
				attrs.design || {},
				attrs.icon_align ? { icon_align: attrs.icon_align } : {}
			),
			popouts: Object.assign( {}, existing.popouts || {}, {
				enable_popouts: attrs.enable_popouts ? 'on' : '',
				popout_items: Array.isArray( attrs.popout_items )
					? attrs.popout_items.slice()
					: [],
			} ),
		} );
	}

	var SOWB_ATTR_MAPPERS = {};

	function flattenSoWidgetData( widgetData ) {
		var flat = {};

		Object.keys( widgetData || {} ).forEach( function ( key ) {
			var value = widgetData[ key ];
			if ( value === null || value === undefined ) {
				return;
			}

			if ( typeof value !== 'object' || Array.isArray( value ) ) {
				flat[ key ] = value;
				return;
			}

			Object.keys( value ).forEach( function ( fieldKey ) {
				flat[ fieldKey ] = value[ fieldKey ];
			} );
		} );

		return flat;
	}

	function coerceWidgetValueToSchema( value, schema ) {
		if ( ! schema || ! schema.type ) {
			return value;
		}

		if ( schema.type === 'boolean' ) {
			return soTruthy( value );
		}
		if ( schema.type === 'number' ) {
			var numberValue = parseInt( value, 10 );
			if ( isNaN( numberValue ) ) {
				return schema.default || 0;
			}
			return numberValue;
		}
		if ( schema.type === 'array' ) {
			return Array.isArray( value ) ? value : [];
		}

		return value;
	}

	function widgetDataToNativeAttrsGeneric( widgetData, nativeBlockName ) {
		var schema = blockAttributes[ nativeBlockName ] || {};
		var flat = flattenSoWidgetData( widgetData );
		var out = {};

		Object.keys( schema ).forEach( function ( key ) {
			if ( ! Object.prototype.hasOwnProperty.call( flat, key ) ) {
				return;
			}

			out[ key ] = coerceWidgetValueToSchema( flat[ key ], schema[ key ] );
		} );

		return out;
	}

	function nativeAttrsToWidgetDataGeneric( attrs, existing, nativeBlockName ) {
		existing = existing || {};
		attrs = attrs || {};
		var schema = blockAttributes[ nativeBlockName ] || {};
		var next = JSON.parse( JSON.stringify( existing ) );

		Object.keys( next ).forEach( function ( sectionKey ) {
			var section = next[ sectionKey ];
			if ( ! section || typeof section !== 'object' || Array.isArray( section ) ) {
				return;
			}

			Object.keys( section ).forEach( function ( fieldKey ) {
				if ( ! Object.prototype.hasOwnProperty.call( attrs, fieldKey ) ) {
					return;
				}

				var fieldSchema = schema[ fieldKey ] || {};
				if ( fieldSchema.type === 'boolean' ) {
					section[ fieldKey ] = attrs[ fieldKey ] ? 'on' : '';
					return;
				}

				section[ fieldKey ] = attrs[ fieldKey ];
			} );
		} );

		return next;
	}

	function widgetClassFromSowbBlock( sowbName ) {
		var match = /^sowb\/rsmspark-content-widgets-(.+)-widget$/.exec( sowbName || '' );
		if ( ! match ) {
			return '';
		}

		var studly = match[ 1 ]
			.split( '-' )
			.map( function ( part ) {
				return part.charAt( 0 ).toUpperCase() + part.slice( 1 );
			} )
			.join( '_' );

		return 'Rsmspark_Content_Widgets_' + studly + '_Widget';
	}

	function createSowbNativeEdit( sowbName, nativeBlockName, NativeEdit, mappers ) {
		mappers = mappers || {};
		var toNative =
			mappers.toNative ||
			function ( widgetData ) {
				return widgetDataToNativeAttrsGeneric( widgetData, nativeBlockName );
			};
		var toWidget =
			mappers.toWidget ||
			function ( attrs, existing ) {
				return nativeAttrsToWidgetDataGeneric(
					attrs,
					existing,
					nativeBlockName
				);
			};
		var widgetClass = widgetClassFromSowbBlock( sowbName );

		return function SowbNativeEdit( props ) {
			var widgetData = ( props.attributes && props.attributes.widgetData ) || {};
			var serialized = JSON.stringify( widgetData );
			var nativeState = useState( function () {
				return toNative( widgetData );
			} );
			var nativeAttrs = nativeState[ 0 ];
			var setNativeAttrs = nativeState[ 1 ];

			useEffect(
				function () {
					setNativeAttrs(
						toNative(
							( props.attributes && props.attributes.widgetData ) || {}
						)
					);
				},
				[ serialized ]
			);

			function setAttributes( patch ) {
				var merged = Object.assign( {}, nativeAttrs, patch );
				setNativeAttrs( merged );
				props.setAttributes( {
					widgetClass:
						props.attributes.widgetClass || widgetClass,
					widgetData: toWidget( merged, props.attributes.widgetData ),
				} );
			}

			return createElement( NativeEdit, {
				clientId: props.clientId,
				attributes: nativeAttrs,
				setAttributes: setAttributes,
				isSelected: props.isSelected,
			} );
		};
	}

	SOWB_ATTR_MAPPERS[ 'sowb/rsmspark-content-widgets-features-widget' ] = {
		toNative: widgetDataToNativeFeaturesAttrs,
		toWidget: nativeFeaturesAttrsToWidgetData,
	};




	function readFooterVisibilitySlugs( widgetData, field ) {
		var flat = flattenSoWidgetData( widgetData || {} );
		var selected = [];

		if ( Array.isArray( flat[ field ] ) ) {
			return flat[ field ];
		}

		Object.keys( flat ).forEach( function ( key ) {
			var prefix = field + '_';
			if ( key.indexOf( prefix ) !== 0 || ! soTruthy( flat[ key ] ) ) {
				return;
			}
			selected.push( key.slice( prefix.length ) );
		} );

		return selected;
	}

	function widgetDataToNativeFooterAttrs( widgetData ) {
		widgetData = widgetData || {};
		var brand =
			widgetData.brand && typeof widgetData.brand === 'object'
				? widgetData.brand
				: {};
		var design =
			widgetData.design && typeof widgetData.design === 'object'
				? widgetData.design
				: {};

		return {
			brand: {
				logo: parseInt( brand.logo, 10 ) || 0,
				name: brand.name || '',
				description: brand.description || '',
			},
			social: Array.isArray( widgetData.social ) ? widgetData.social : [],
			columns: Array.isArray( widgetData.columns ) ? widgetData.columns : [],
			copyright: widgetData.copyright || '',
			region: widgetData.region || 'default',
			show_on: readFooterVisibilitySlugs( widgetData, 'show_on' ),
			hide_on: readFooterVisibilitySlugs( widgetData, 'hide_on' ),
			show_on_post_types: Array.isArray( widgetData.show_on_post_types )
				? widgetData.show_on_post_types
				: [],
			hide_on_post_types: Array.isArray( widgetData.hide_on_post_types )
				? widgetData.hide_on_post_types
				: [],
			design: design,
		};
	}

	function nativeFooterAttrsToWidgetData( attrs, existing ) {
		existing = existing || {};
		attrs = attrs || {};

		var next = Object.assign( {}, existing, {
			region: attrs.region || 'default',
			copyright: attrs.copyright || '',
			social: Array.isArray( attrs.social ) ? attrs.social : [],
			columns: Array.isArray( attrs.columns ) ? attrs.columns : [],
			show_on_post_types: Array.isArray( attrs.show_on_post_types )
				? attrs.show_on_post_types
				: [],
			hide_on_post_types: Array.isArray( attrs.hide_on_post_types )
				? attrs.hide_on_post_types
				: [],
			brand: Object.assign( {}, existing.brand || {}, attrs.brand || {} ),
			design: Object.assign( {}, existing.design || {}, attrs.design || {} ),
		} );

		Object.keys( next ).forEach( function ( key ) {
			var showPrefix = 'show_on_';
			var hidePrefix = 'hide_on_';
			if (
				key.indexOf( showPrefix ) === 0 ||
				key.indexOf( hidePrefix ) === 0
			) {
				delete next[ key ];
			}
		} );

		( attrs.show_on || [] ).forEach( function ( slug ) {
			next[ 'show_on_' + slug ] = true;
		} );
		( attrs.hide_on || [] ).forEach( function ( slug ) {
			next[ 'hide_on_' + slug ] = true;
		} );

		return next;
	}

	SOWB_ATTR_MAPPERS[ 'sowb/rsmspark-content-widgets-footer-widget' ] = {
		toNative: widgetDataToNativeFooterAttrs,
		toWidget: nativeFooterAttrsToWidgetData,
	};

	function buildSowbNativeBridges() {
		var bridges = {};
		var hideList = config.hideSowbBlocks || [];

		hideList.forEach( function ( sowbName ) {
			var nativeName = sowbToNativeBlockName( sowbName );
			if ( ! nativeName || ! editComponents[ nativeName ] ) {
				return;
			}

			bridges[ sowbName ] = createSowbNativeEdit(
				sowbName,
				nativeName,
				editComponents[ nativeName ],
				SOWB_ATTR_MAPPERS[ sowbName ]
			);
		} );

		return bridges;
	}

	var sowbNativeBridgesCache = null;

	function getSowbNativeBridges() {
		if ( ! sowbNativeBridgesCache ) {
			sowbNativeBridgesCache = buildSowbNativeBridges();
		}
		return sowbNativeBridgesCache;
	}

	function sowbToNativeBlockName( sowbName ) {
		if (
			window.RSMBlockBranding &&
			typeof window.RSMBlockBranding.sowbToNativeBlockName === 'function'
		) {
			return window.RSMBlockBranding.sowbToNativeBlockName( sowbName );
		}

		var match = /^sowb\/rsmspark-content-widgets-(.+)-widget$/.exec( sowbName || '' );
		return match ? 'rsm-content-widgets/' + match[ 1 ] : '';
	}

	function FeaturesEdit( props ) {
		var attrs = props.attributes;
		var features = Array.isArray( attrs.features )
			? attrs.features.map( normalizeFeatureForEditor )
			: [];
		var popoutItems = Array.isArray( attrs.popout_items )
			? attrs.popout_items.map( normalizePopoutItemForEditor )
			: [];
		var popoutsEnabled = !! attrs.enable_popouts;
		var accordionOpen = useRepeaterAccordionOpenState();
		useSeedRepeaterDefaults(
			'rsm-content-widgets/features',
			'features',
			attrs,
			props.setAttributes
		);

		useEffect(
			function () {
				if ( ! Array.isArray( attrs.features ) ) {
					return;
				}

				var nextFeatures = attrs.features.map( normalizeFeatureForEditor );
				var featuresChanged = nextFeatures.some( function ( feature, index ) {
					var current = attrs.features[ index ] || {};
					return (
						( feature.description || '' ) !== ( current.description || '' )
					);
				} );

				var nextPopouts = Array.isArray( attrs.popout_items )
					? attrs.popout_items.map( normalizePopoutItemForEditor )
					: [];
				var popoutsChanged =
					Array.isArray( attrs.popout_items ) &&
					nextPopouts.some( function ( item, index ) {
						var current = attrs.popout_items[ index ] || {};
						return (
							( item.popout_content || '' ) !==
							( current.popout_content || '' )
						);
					} );

				if ( ! featuresChanged && ! popoutsChanged ) {
					return;
				}

				var patch = {};
				if ( featuresChanged ) {
					patch.features = nextFeatures;
				}
				if ( popoutsChanged ) {
					patch.popout_items = nextPopouts;
				}
				props.setAttributes( patch );
			},
			[ props.clientId ]
		);

		function updateFeature( index, field, value ) {
			var next = features.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			var patch = { features: next };
			if ( popoutsEnabled && field === 'popout_hot' && value ) {
				patch.popout_items = ensurePopoutRows(
					popoutItems,
					index,
					featureChipPlainTitle( next[ index ].title )
				);
				accordionOpen.setOpen( index, true );
			}
			props.setAttributes( patch );
		}

		function updatePopoutItem( index, field, value ) {
			var next = popoutItems.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { popout_items: next } );
		}

		function updatePopoutMedia( index, media ) {
			var next = popoutItems.slice();
			next[ index ] = Object.assign( {}, next[ index ], {
				popout_image: parseInt( media && media.id ? media.id : 0, 10 ) || 0,
				popout_image_src:
					media && media.url ? String( media.url ) : '',
			} );
			props.setAttributes( { popout_items: next } );
		}

		function addPopoutItem() {
			props.setAttributes( {
				popout_items: popoutItems.concat( [ defaultPopoutItem( '' ) ] ),
			} );
		}

		function removePopoutItem( index ) {
			props.setAttributes( {
				popout_items: popoutItems.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		function addFeature() {
			props.setAttributes( {
				features: features.concat( [
					{
						enabled: true,
						title: t( 'feature', 'Feature' ) + ' ' + ( features.length + 1 ),
						description: '',
						title_icon: 'dashicons dashicons-star-filled',
						title_image: 0,
						accent_color: '#2563eb',
						icon_style: 'glyph',
						icon_color: '',
						icon_badge_circle_color: '',
						icon_badge_text_color: '#ffffff',
						icon_badge_glyph: '',
						chip_shape: 'inherit',
						popout_hot: false,
					},
				] ),
			} );
		}

		function removeFeature( index ) {
			var nextPopouts = popoutItems.filter( function ( _, i ) {
				return i !== index;
			} );

			props.setAttributes( {
				features: features.filter( function ( _, i ) {
					return i !== index;
				} ),
				popout_items: nextPopouts,
			} );
		}

		function togglePopoutsEnabled( value ) {
			var patch = { enable_popouts: value };
			if ( value ) {
				patch.popout_items = syncPopoutRowsForHotChips(
					features,
					popoutItems
				);
			}
			props.setAttributes( patch );
		}

		var popoutControlProps = {
			popoutsEnabled: popoutsEnabled,
			popoutItems: popoutItems,
			instanceId: props.clientId,
			onTogglePopouts: togglePopoutsEnabled,
			onChangePopoutItem: updatePopoutItem,
			onChangePopoutMedia: updatePopoutMedia,
			onRemovePopoutItem: removePopoutItem,
			onAddPopoutItem: addPopoutItem,
		};

		var settings = blockInspectorSettings(
			'features',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'layout', 'Layout' ), initialOpen: true },
					createElement( SelectControl, {
						label: t( 'chipLayout', 'Chip layout' ),
						value: attrs.chip_layout || 'stacked',
						options: [
							{ label: t( 'chipLayoutStacked', 'Stacked (icon above text)' ), value: 'stacked' },
							{ label: t( 'chipLayoutMediaLeft', 'Icon left, text top-aligned' ), value: 'media-left' },
						],
						onChange: function ( value ) {
							props.setAttributes( { chip_layout: value } );
						},
					} ),
					( attrs.chip_layout || 'stacked' ) === 'stacked'
						? createElement( SelectControl, {
								label: t( 'iconAlign', 'Icon alignment' ),
								value: attrs.icon_align || 'center',
								options: [
									{ label: t( 'alignLeft', 'Left' ), value: 'left' },
									{ label: t( 'alignCenter', 'Center' ), value: 'center' },
									{ label: t( 'alignRight', 'Right' ), value: 'right' },
								],
								onChange: function ( value ) {
									props.setAttributes( { icon_align: value } );
								},
						  } )
						: null,
					createElement( SelectControl, {
						label: t( 'columns', 'Columns' ),
						value: String( attrs.columns || 3 ),
						options: [
							{ label: '1', value: '1' },
							{ label: '2', value: '2' },
							{ label: '3', value: '3' },
							{ label: '4', value: '4' },
							{ label: '5', value: '5' },
							{ label: '6', value: '6' },
						],
						onChange: function ( value ) {
							props.setAttributes( { columns: parseInt( value, 10 ) || 3 } );
						},
					} )
				),
				createElement(
					PanelBody,
					{
						title: t( 'popouts', 'Chip popouts' ),
						initialOpen: popoutsEnabled,
					},
					featuresPopoutControls(
						Object.assign( {}, popoutControlProps, {
							globalsOnly: true,
						} )
					)
				)
			),
			Object.assign( {}, blockJsonContext( props, 'rsm-content-widgets/features' ), {
				buildExport: function ( featureAttrs ) {
					return buildFeaturesExportPayload(
						featureAttrs,
						featureAttrs.title || t( 'features', 'Features' )
					);
				},
				parseImport: parseFeaturesImportPayload,
				mergeImport: function ( payload, current ) {
					return mergeFeaturesImportConfig(
						payload.attributes ||
							widgetDataToNativeFeaturesAttrs( payload.config || {} ),
						current || props.attributes || {}
					);
				},
			} )
		);

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/features',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement(
						'p',
						{ className: 'rsmspark-cw-block-editor__heading' },
						t( 'content', 'Content' )
					),
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Section title' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					createElement(
						'div',
						{
							className:
								'rsmspark-cw-block-editor__features-popouts-toggle',
						},
						createElement( ToggleControl, {
							label: t( 'enablePopouts', 'Enable chip popouts' ),
							help: t(
								'enablePopoutsHelp',
								'Hot chips open a detail modal. Mark chips as Hot to edit popout content under each feature.'
							),
							checked: popoutsEnabled,
							onChange: togglePopoutsEnabled,
						} )
					),
					createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__features-accordion' },
						features.map( function ( feature, index ) {
							return createElement( FeatureAccordionEditor, {
								key: 'feature-' + index,
								feature: feature,
								index: index,
								instanceId: props.clientId,
								popoutsEnabled: popoutsEnabled,
								popoutItem: popoutItems[ index ] || null,
								disableRemove: features.length <= 1,
								isOpen: accordionOpen.isOpen( index ),
								onToggle: function () {
									accordionOpen.toggleOpen( index );
								},
								onChange: function ( field, value ) {
									updateFeature( index, field, value );
								},
								onRemove: function () {
									removeFeature( index );
								},
								onChangePopoutItem: function ( field, value ) {
									updatePopoutItem( index, field, value );
								},
								onChangePopoutMedia: function ( media ) {
									updatePopoutMedia( index, media );
								},
								onEnableHot: function () {
									updateFeature( index, 'popout_hot', true );
								},
							} );
						} )
					),
					createElement(
						Button,
						{ variant: 'secondary', onClick: addFeature },
						t( 'addItem', 'Add feature' )
					)
				)
			)
		);
	}

	function TabsEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		var accordionOpen = useRepeaterAccordionOpenState();
		useSeedRepeaterDefaults(
			'rsm-content-widgets/tabs',
			'items',
			attrs,
			props.setAttributes
		);

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		function addItem() {
			accordionOpen.setOpen( items.length, true );
			props.setAttributes( {
				items: items.concat( [
					{
						title: t( 'tab', 'Tab' ) + ' ' + ( items.length + 1 ),
						content: '',
					},
				] ),
			} );
		}

		function removeItem( index ) {
			props.setAttributes( {
				items: items.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		var settings = blockInspectorSettings(
			'tabs',
			null,
			blockJsonContext( props, 'rsm-content-widgets/tabs' )
		);

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/tabs',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement(
						'p',
						{ className: 'rsmspark-cw-block-editor__heading' },
						t( 'content', 'Content' )
					),
					createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__repeater-accordion-list' },
						items.map( function ( item, index ) {
							var displayTitle =
								String( item.title || '' ).trim() ||
								t( 'tab', 'Tab' ) + ' ' + ( index + 1 );

							return createElement(
								GenericRepeaterAccordionEditor,
								{
									key: 'tab-item-' + index,
									indexLabel: t( 'tab', 'Tab' ) + ' ' + ( index + 1 ),
									displayTitle: displayTitle,
									isOpen: accordionOpen.isOpen( index ),
									onToggle: function () {
										accordionOpen.toggleOpen( index );
									},
									disableRemove: items.length <= 1,
									onRemove: function () {
										removeItem( index );
									},
								},
								createElement( TextControl, {
									label: t( 'title', 'Title' ),
									value: item.title || '',
									onChange: function ( value ) {
										updateItem( index, 'title', value );
									},
								} ),
								createElement( RichTextProfileField, {
									label: t( 'description', 'Content' ),
									profile: 'body',
									fieldKey: 'tabs-content-' + index,
									instanceId: props.clientId,
									value: item.content || '',
									onChange: function ( value ) {
										updateItem( index, 'content', value );
									},
								} )
							);
						} )
					),
					createElement(
						Button,
						{ variant: 'secondary', onClick: addItem },
						t( 'addItem', 'Add item' )
					)
				)
			)
		);
	}

	function blockColorField( label, attrKey, attrs, setAttributes ) {
		return createElement(
			BaseControl,
			{ label: label },
			createElement( ColorPalette, {
				value: attrs[ attrKey ] || '',
				onChange: function ( value ) {
					var next = {};
					next[ attrKey ] = value || '';
					setAttributes( next );
				},
				clearable: true,
			} )
		);
	}


	function AccordionEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		var accordionOpen = useRepeaterAccordionOpenState();
		useSeedRepeaterDefaults(
			'rsm-content-widgets/accordion',
			'items',
			attrs,
			props.setAttributes
		);

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		function addItem() {
			accordionOpen.setOpen( items.length, true );
			props.setAttributes( {
				items: items.concat( [
					{
						title: t( 'section', 'Section' ) + ' ' + ( items.length + 1 ),
						content: '',
					},
				] ),
			} );
		}

		function removeItem( index ) {
			props.setAttributes( {
				items: items.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		var settings = blockInspectorSettings(
			'accordion',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'accordionItemColors', 'Accordion item' ), initialOpen: false },
					createElement( ToggleControl, {
						label: 'FAQ mode',
						help: 'Adds FAQ styling and FAQPage structured data on the front end.',
						checked: !! attrs.faq_mode,
						onChange: function ( value ) {
							props.setAttributes( { faq_mode: value } );
						},
					} ),
					blockColorField(
						t( 'itemBackground', 'Item background' ),
						'item_bg',
						attrs,
						props.setAttributes
					)
				),
				createElement(
					PanelBody,
					{ title: t( 'accordionRowBorders', 'Row borders' ), initialOpen: false },
					createElement( ToggleControl, {
						label: t( 'showRowBorders', 'Show row borders' ),
						checked: !! attrs.item_border_enable,
						onChange: function ( value ) {
							props.setAttributes( { item_border_enable: value } );
						},
					} ),
					createElement( RangeControl, {
						label: t( 'rowBorderWidth', 'Border width (px)' ),
						value: attrs.item_border_width != null ? attrs.item_border_width : 1,
						onChange: function ( value ) {
							props.setAttributes( { item_border_width: value } );
						},
						min: 1,
						max: 8,
					} ),
					createElement(
						BaseControl,
						{ label: t( 'rowBorderColor', 'Border color' ) },
						createElement( ColorPalette, {
							value: attrs.item_border_color || '#e5e7eb',
							onChange: function ( value ) {
								props.setAttributes( { item_border_color: value || '#e5e7eb' } );
							},
							clearable: false,
						} )
					),
					createElement( SelectControl, {
						label: t( 'rowCorners', 'Row corners' ),
						value: attrs.item_border_radius || 'square',
						options: [
							{ label: t( 'square', 'Square' ), value: 'square' },
							{ label: t( 'rounded', 'Rounded' ), value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { item_border_radius: value } );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: t( 'accordionSelectorColors', 'Section selector' ), initialOpen: true },
					blockColorField(
						t( 'selectorBackground', 'Background' ),
						'trigger_bg',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'selectorText', 'Text' ),
						'trigger_color',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'selectorHoverBackground', 'Hover background' ),
						'trigger_bg_hover',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'selectorHoverText', 'Hover text' ),
						'trigger_color_hover',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'selectorActiveBackground', 'Active background' ),
						'trigger_bg_active',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'selectorActiveText', 'Active text' ),
						'trigger_color_active',
						attrs,
						props.setAttributes
					)
				),
				createElement(
					PanelBody,
					{ title: t( 'accordionPanelColors', 'Expanded content' ), initialOpen: false },
					blockColorField(
						t( 'panelBackground', 'Background' ),
						'panel_bg',
						attrs,
						props.setAttributes
					),
					blockColorField(
						t( 'panelText', 'Text' ),
						'panel_color',
						attrs,
						props.setAttributes
					)
				),
				createElement(
					PanelBody,
					{ title: t( 'accordionPanelDivider', 'Open section divider' ), initialOpen: false },
					createElement( ToggleControl, {
						label: t( 'showPanelDivider', 'Show divider line' ),
						help: t(
							'panelDividerHelp',
							'Line between the open selector and its content. Off by default.'
						),
						checked: !! attrs.panel_border_enable,
						onChange: function ( value ) {
							props.setAttributes( { panel_border_enable: value } );
						},
					} ),
					createElement(
						BaseControl,
						{ label: t( 'dividerColor', 'Divider color' ) },
						createElement( ColorPalette, {
							value: attrs.panel_border_color || '#e5e7eb',
							onChange: function ( value ) {
								props.setAttributes( { panel_border_color: value || '#e5e7eb' } );
							},
							clearable: false,
						} )
					)
				),
				createElement(
					PanelBody,
					{ title: t( 'accordionWidgetBorder', 'Widget border' ), initialOpen: false },
					createElement( ToggleControl, {
						label: t( 'showWidgetBorder', 'Show widget border' ),
						help: t(
							'widgetBorderHelp',
							'Optional border around the entire accordion. Off by default. SiteOrigin page-builder chrome is always removed.'
						),
						checked: !! attrs.container_border,
						onChange: function ( value ) {
							props.setAttributes( { container_border: value } );
						},
					} ),
					createElement( RangeControl, {
						label: t( 'widgetBorderWidth', 'Border width (px)' ),
						value: attrs.container_border_width != null ? attrs.container_border_width : 1,
						onChange: function ( value ) {
							props.setAttributes( { container_border_width: value } );
						},
						min: 1,
						max: 8,
					} ),
					createElement(
						BaseControl,
						{ label: t( 'widgetBorderColor', 'Border color' ) },
						createElement( ColorPalette, {
							value: attrs.container_border_color || '#e5e7eb',
							onChange: function ( value ) {
								props.setAttributes( { container_border_color: value || '#e5e7eb' } );
							},
							clearable: false,
						} )
					),
					createElement( SelectControl, {
						label: t( 'widgetCorners', 'Widget corners' ),
						value: attrs.container_border_radius || 'square',
						options: [
							{ label: t( 'square', 'Square' ), value: 'square' },
							{ label: t( 'rounded', 'Rounded' ), value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { container_border_radius: value } );
						},
					} )
				)
			)
		, blockJsonContext( props, 'rsm-content-widgets/accordion' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/accordion',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement(
						'p',
						{ className: 'rsmspark-cw-block-editor__heading' },
						t( 'content', 'Content' )
					),
					createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__repeater-accordion-list' },
						items.map( function ( item, index ) {
							var isCategory = item.item_type === 'category';
							var displayTitle =
								String( item.title || '' ).trim() ||
								( isCategory
									? t( 'accordionCategory', 'Category' )
									: t( 'section', 'Section' ) + ' ' + ( index + 1 ) );

							return createElement(
								GenericRepeaterAccordionEditor,
								{
									key: 'accordion-item-' + index,
									indexLabel: isCategory
										? t( 'accordionCategory', 'Category' )
										: t( 'section', 'Section' ) + ' ' + ( index + 1 ),
									displayTitle: displayTitle,
									meta: isCategory
										? createElement(
												'span',
												{
													className:
														'rsmspark-cw-repeater-accordion__meta-tag',
												},
												t( 'accordionCategory', 'Category' )
										  )
										: null,
									isOpen: accordionOpen.isOpen( index ),
									onToggle: function () {
										accordionOpen.toggleOpen( index );
									},
									disableRemove: items.length <= 1,
									onRemove: function () {
										removeItem( index );
									},
								},
								createElement( SelectControl, {
									label: t( 'accordionRowType', 'Row type' ),
									value: item.item_type || 'item',
									options: [
										{
											label: t( 'section', 'Section' ),
											value: 'item',
										},
										{
											label: t(
												'accordionCategory',
												'Category'
											),
											value: 'category',
										},
									],
									onChange: function ( value ) {
										updateItem( index, 'item_type', value );
									},
								} ),
								createElement( TextControl, {
									label: isCategory
										? t( 'accordionCategory', 'Category' )
										: t( 'title', 'Title' ),
									value: item.title || '',
									onChange: function ( value ) {
										updateItem( index, 'title', value );
									},
								} ),
								isCategory
									? null
									: createElement( RichTextProfileField, {
											label: t( 'description', 'Content' ),
											profile: 'body',
											fieldKey: 'accordion-content-' + index,
											instanceId: props.clientId,
											value: item.content || '',
											onChange: function ( value ) {
												updateItem( index, 'content', value );
											},
									  } )
							);
						} )
					),
					createElement(
						Button,
						{ variant: 'secondary', onClick: addItem },
						t( 'addItem', 'Add item' )
					)
				)
			)
		);
	}

	function IconBoxGridEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		useSeedRepeaterDefaults(
			'rsm-content-widgets/icon-box-grid',
			'items',
			attrs,
			props.setAttributes
		);

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		function removeItem( index ) {
			props.setAttributes( {
				items: items.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		var settings = blockInspectorSettings(
			'icon-box-grid',
			createElement(
				PanelBody,
				{ title: t( 'layout', 'Layout' ), initialOpen: true },
				createElement( SelectControl, {
					label: t( 'columns', 'Columns' ),
					value: String( attrs.columns || '3' ),
					options: [
						{ label: '2', value: '2' },
						{ label: '3', value: '3' },
						{ label: '4', value: '4' },
					],
					onChange: function ( value ) {
						props.setAttributes( { columns: value } );
					},
				} )
			)
		, blockJsonContext( props, 'rsm-content-widgets/icon-box-grid' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/icon-box-grid',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Section title' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					items.map( function ( item, index ) {
						return createElement(
							'div',
							{
								key: 'icon-box-' + index,
								className: 'rsmspark-cw-block-editor__repeater-item',
							},
							createElement(
								'div',
								{ className: 'rsmspark-cw-block-editor__repeater-head' },
								createElement(
									'p',
									{ className: 'rsmspark-cw-block-editor__repeater-title' },
									t( 'box', 'Box' ) + ' ' + ( index + 1 )
								),
								createElement(
									Button,
									{
										variant: 'link',
										isDestructive: true,
										onClick: function () {
											removeItem( index );
										},
										disabled: items.length <= 1,
									},
									t( 'removeItem', 'Remove' )
								)
							),
							createElement( DashiconSelectControl, {
								label: t( 'selectIcon', 'Icon' ),
								value: item.icon || '',
								onChange: function ( value ) {
									updateItem( index, 'icon', value );
								},
							} ),
							createElement( TextControl, {
								label: t( 'title', 'Title' ),
								value: item.title || '',
								onChange: function ( value ) {
									updateItem( index, 'title', value );
								},
							} ),
							createElement( TextareaControl, {
								label: t( 'description', 'Description' ),
								value: item.description || '',
								onChange: function ( value ) {
									updateItem( index, 'description', value );
								},
								rows: 3,
							} )
						);
					} ),
					createElement(
						Button,
						{
							variant: 'secondary',
							onClick: function () {
								props.setAttributes( {
									items: items.concat( [
										{
											icon: 'dashicons dashicons-yes-alt',
											title: '',
											description: '',
										},
									] ),
								} );
							},
						},
						t( 'addItem', 'Add item' )
					)
				)
			)
		);
	}

	function LogoGridItemEditor( props ) {
		var item = props.item;
		var preview = useMediaPreview( item.image );

		return createElement(
			'div',
			{ className: 'rsmspark-cw-block-editor__repeater-item' },
			createElement(
				'div',
				{ className: 'rsmspark-cw-block-editor__repeater-head' },
				createElement(
					'p',
					{ className: 'rsmspark-cw-block-editor__repeater-title' },
					t( 'logo', 'Logo' ) + ' ' + ( props.index + 1 )
				),
				createElement(
					Button,
					{
						variant: 'link',
						isDestructive: true,
						onClick: props.onRemove,
						disabled: props.disableRemove,
					},
					t( 'removeItem', 'Remove' )
				)
			),
			createElement( MediaImageControl, {
				label: t( 'image', 'Image' ),
				value: item.image,
				previewUrl: preview,
				onChange: props.onMediaChange,
			} ),
			createElement( TextControl, {
				label: t( 'name', 'Name' ),
				help: 'Hover overlay, fallback text, and default alt.',
				value: item.name || '',
				onChange: function ( value ) {
					props.onChange( 'name', value );
				},
			} ),
			createElement( TextControl, {
				label: 'Alt text',
				help: 'Leave empty to use the name.',
				value: item.alt || '',
				onChange: function ( value ) {
					props.onChange( 'alt', value );
				},
			} ),
			createElement( TextControl, {
				label: 'Link URL',
				help: 'Optional. Makes the whole cell a link.',
				value: item.url || '',
				onChange: function ( value ) {
					props.onChange( 'url', value );
				},
			} )
		);
	}

	function LogoGridEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		useSeedRepeaterDefaults(
			'rsm-content-widgets/logo-grid',
			'items',
			attrs,
			props.setAttributes
		);

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		var settings = blockInspectorSettings(
			'logo-grid',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'layout', 'Layout' ), initialOpen: true },
					createElement( SelectControl, {
						label: t( 'columns', 'Columns' ),
						value: String( attrs.columns || '4' ),
						options: [
							{ label: '2', value: '2' },
							{ label: '3', value: '3' },
							{ label: '4', value: '4' },
							{ label: '5', value: '5' },
							{ label: '6', value: '6' },
						],
						onChange: function ( value ) {
							props.setAttributes( { columns: value } );
						},
					} ),
					createElement( SelectControl, {
						label: t( 'layout', 'Layout' ),
						value: 'grid',
						options: [ { label: 'Grid', value: 'grid' } ],
						onChange: function () {
							props.setAttributes( { layout: 'grid' } );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: 'Headline', initialOpen: false },
					createElement(
						BaseControl,
						{ label: 'Headline color' },
						createElement( ColorPalette, {
							value: attrs.title_color || '',
							onChange: function ( value ) {
								props.setAttributes( { title_color: value || '' } );
							},
							clearable: true,
						} )
					),
					createElement( RangeControl, {
						label: 'Headline size (px)',
						help: '0 uses the responsive default size.',
						value: attrs.title_size || 0,
						onChange: function ( value ) {
							props.setAttributes( { title_size: value } );
						},
						min: 0,
						max: 72,
					} )
				),
				createElement(
					PanelBody,
					{ title: 'Cell borders', initialOpen: false },
					createElement( ToggleControl, {
						label: 'Show cell borders',
						checked: !! attrs.border_enable,
						onChange: function ( value ) {
							props.setAttributes( { border_enable: value } );
						},
					} ),
					createElement( RangeControl, {
						label: 'Border width (px)',
						value: attrs.border_width != null ? attrs.border_width : 1,
						onChange: function ( value ) {
							props.setAttributes( { border_width: value } );
						},
						min: 1,
						max: 8,
					} ),
					createElement(
						BaseControl,
						{ label: 'Border color' },
						createElement( ColorPalette, {
							value: attrs.border_color || '#e2e8f0',
							onChange: function ( value ) {
								props.setAttributes( { border_color: value || '#e2e8f0' } );
							},
							clearable: false,
						} )
					),
					createElement( SelectControl, {
						label: 'Cell corners',
						value: attrs.border_radius || 'square',
						options: [
							{ label: 'Square', value: 'square' },
							{ label: 'Rounded', value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { border_radius: value } );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: 'Widget border', initialOpen: false },
					createElement( ToggleControl, {
						label: 'Show widget border',
						help: 'Optional border around the entire grid. Off by default. SiteOrigin page-builder chrome is always removed.',
						checked: !! attrs.container_border,
						onChange: function ( value ) {
							props.setAttributes( { container_border: value } );
						},
					} ),
					createElement( RangeControl, {
						label: 'Border width (px)',
						value: attrs.container_border_width != null ? attrs.container_border_width : 1,
						onChange: function ( value ) {
							props.setAttributes( { container_border_width: value } );
						},
						min: 1,
						max: 8,
					} ),
					createElement(
						BaseControl,
						{ label: 'Border color' },
						createElement( ColorPalette, {
							value: attrs.container_border_color || '#e2e8f0',
							onChange: function ( value ) {
								props.setAttributes( { container_border_color: value || '#e2e8f0' } );
							},
							clearable: false,
						} )
					),
					createElement( SelectControl, {
						label: 'Widget corners',
						value: attrs.container_border_radius || 'square',
						options: [
							{ label: 'Square', value: 'square' },
							{ label: 'Rounded', value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { container_border_radius: value } );
						},
					} )
				)
			)
		, blockJsonContext( props, 'rsm-content-widgets/logo-grid' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/logo-grid',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Section title' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					createElement(
						'p',
						{ className: 'description' },
						'Each logo is one cell: image (or name as text fallback), optional link wrapping the cell. Pro options add horizontal scroll, hover name overlay, and grayscale-until-hover.'
					),
					items.map( function ( item, index ) {
						return createElement( LogoGridItemEditor, {
							key: 'logo-' + index,
							item: item,
							index: index,
							disableRemove: items.length <= 1,
							onRemove: function () {
								props.setAttributes( {
									items: items.filter( function ( _, i ) {
										return i !== index;
									} ),
								} );
							},
							onChange: function ( field, value ) {
								updateItem( index, field, value );
							},
							onMediaChange: function ( media ) {
								var next = items.slice();
								next[ index ] = Object.assign( {}, next[ index ], {
									image: media && media.id ? String( media.id ) : '0',
									image_src: media && media.url ? String( media.url ) : '',
								} );
								props.setAttributes( { items: next } );
							},
						} );
					} ),
					createElement(
						Button,
						{
							variant: 'secondary',
							onClick: function () {
								props.setAttributes( {
									items: items.concat( [
										{
											image: 0,
											image_src: '',
											name: '',
											url: '',
											alt: '',
										},
									] ),
								} );
							},
						},
						t( 'addItem', 'Add item' )
					)
				)
			)
		);
	}




	function StatsCounterEdit( props ) {
		var attrs = props.attributes;
		var items = Array.isArray( attrs.items ) ? attrs.items.slice() : [];
		useSeedRepeaterDefaults(
			'rsm-content-widgets/stats-counter',
			'items',
			attrs,
			props.setAttributes
		);

		function updateItem( index, field, value ) {
			var next = items.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { items: next } );
		}

		var settings = blockInspectorSettings(
			'stats-counter',
			createElement(
				PanelBody,
				{ title: t( 'layout', 'Layout' ), initialOpen: true },
				createElement( SelectControl, {
					label: t( 'columns', 'Columns' ),
					value: String( attrs.columns || '3' ),
					options: [
						{ label: '2', value: '2' },
						{ label: '3', value: '3' },
						{ label: '4', value: '4' },
					],
					onChange: function ( value ) {
						props.setAttributes( { columns: value } );
					},
				} )
			)
		, blockJsonContext( props, 'rsm-content-widgets/stats-counter' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/stats-counter',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Section title' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					createElement(
						'p',
						{ className: 'description' },
						'Each metric shows a large value line (prefix + number + suffix) with a smaller label underneath — or a pie/donut chart when Display as is set. Value is treated as percent (0–100) for charts. Numbers render as entered; charts use the current value as a percent.'
					),
					items.map( function ( item, index ) {
						return createElement(
							'div',
							{
								key: 'stat-' + index,
								className: 'rsmspark-cw-block-editor__repeater-item',
							},
							createElement(
								'div',
								{ className: 'rsmspark-cw-block-editor__repeater-head' },
								createElement(
									'p',
									{ className: 'rsmspark-cw-block-editor__repeater-title' },
									t( 'stat', 'Stat' ) + ' ' + ( index + 1 )
								),
								createElement(
									Button,
									{
										variant: 'link',
										isDestructive: true,
										onClick: function () {
											props.setAttributes( {
												items: items.filter( function ( _, i ) {
													return i !== index;
												} ),
											} );
										},
										disabled: items.length <= 1,
									},
									t( 'removeItem', 'Remove' )
								)
							),
							createElement( TextControl, {
								label: 'Value',
								help: 'Main number, or percent (0–100) for pie/donut.',
								value: item.value || '',
								onChange: function ( value ) {
									updateItem( index, 'value', value );
								},
							} ),
							createElement( SelectControl, {
								label: 'Display as',
								value: item.display || 'number',
								options: [
									{ label: 'Number', value: 'number' },
									{ label: 'Pie chart', value: 'pie' },
									{ label: 'Donut chart', value: 'donut' },
								],
								onChange: function ( value ) {
									updateItem( index, 'display', value );
								},
							} ),
							createElement(
								BaseControl,
								{ label: 'Number color' },
								createElement( ColorPalette, {
									value: item.value_color || '',
									onChange: function ( value ) {
										updateItem( index, 'value_color', value || '' );
									},
									clearable: true,
								} )
							),
							createElement( TextControl, {
								label: 'Prefix',
								help: 'Text before the number (e.g. $ or #).',
								value: item.prefix || '',
								onChange: function ( value ) {
									updateItem( index, 'prefix', value );
								},
							} ),
							createElement(
								BaseControl,
								{ label: 'Prefix color' },
								createElement( ColorPalette, {
									value: item.prefix_color || '',
									onChange: function ( value ) {
										updateItem( index, 'prefix_color', value || '' );
									},
									clearable: true,
								} )
							),
							createElement( TextControl, {
								label: 'Suffix',
								help: 'Text after the number (e.g. % or +). Not animated.',
								value: item.suffix || '',
								onChange: function ( value ) {
									updateItem( index, 'suffix', value );
								},
							} ),
							createElement(
								BaseControl,
								{ label: 'Suffix color' },
								createElement( ColorPalette, {
									value: item.suffix_color || '',
									onChange: function ( value ) {
										updateItem( index, 'suffix_color', value || '' );
									},
									clearable: true,
								} )
							),
							createElement( TextControl, {
								label: t( 'label', 'Label' ),
								help: 'Caption below the value.',
								value: item.label || '',
								onChange: function ( value ) {
									updateItem( index, 'label', value );
								},
							} )
						);
					} ),
					createElement(
						Button,
						{
							variant: 'secondary',
							onClick: function () {
								props.setAttributes( {
									items: items.concat( [
										{
											value: '',
											prefix: '',
											suffix: '',
											label: '',
											display: 'number',
											value_color: '',
											prefix_color: '',
											suffix_color: '',
										},
									] ),
								} );
							},
						},
						t( 'addItem', 'Add item' )
					)
				)
			)
		);
	}

	function EventCountdownEdit( props ) {
		var attrs = props.attributes;
		var validationError = '';

		if ( ! attrs.end_date ) {
			validationError = 'Set an end date (YYYY-MM-DD) in your site timezone.';
		} else if ( ! /^\d{4}-\d{2}-\d{2}$/.test( String( attrs.end_date ) ) ) {
			validationError = 'End date must use YYYY-MM-DD format.';
		} else if (
			attrs.end_time &&
			! /^\d{2}:\d{2}(:\d{2})?$/.test( String( attrs.end_time ) )
		) {
			validationError = 'End time must use HH:MM or HH:MM:SS format.';
		}

		var settings = blockInspectorSettings(
			'event-countdown',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: 'Countdown', initialOpen: true },
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Title (optional)' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					createElement( TextControl, {
						label: 'Expired message',
						value: attrs.expired_message || '',
						onChange: function ( value ) {
							props.setAttributes( { expired_message: value } );
						},
					} ),
					createElement( ToggleControl, {
						label: 'Hide widget when expired',
						checked: !! attrs.hide_when_expired,
						onChange: function ( value ) {
							props.setAttributes( { hide_when_expired: value } );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: 'Appearance', initialOpen: false },
					createElement(
						BaseControl,
						{ label: 'Background color' },
						createElement( ColorPalette, {
							value: attrs.background_color || '',
							onChange: function ( value ) {
								props.setAttributes( { background_color: value || '' } );
							},
							clearable: true,
						} )
					),
					createElement(
						BaseControl,
						{ label: 'Countdown text color' },
						createElement( ColorPalette, {
							value: attrs.text_color || '',
							onChange: function ( value ) {
								props.setAttributes( { text_color: value || '' } );
							},
							clearable: true,
						} )
					),
					createElement(
						BaseControl,
						{ label: 'Title color' },
						createElement( ColorPalette, {
							value: attrs.title_color || '',
							onChange: function ( value ) {
								props.setAttributes( { title_color: value || '' } );
							},
							clearable: true,
						} )
					)
				)
			)
		, blockJsonContext( props, 'rsm-content-widgets/event-countdown' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/event-countdown',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					validationError
						? createElement( Notice, {
								status: 'warning',
								isDismissible: false,
						  }, validationError )
						: null,
					createElement(
						BaseControl,
						{ label: 'End date', help: 'Site timezone.' },
						createElement( 'input', {
							type: 'date',
							className: 'components-text-control__input',
							value: attrs.end_date || '',
							onChange: function ( event ) {
								props.setAttributes( { end_date: event.target.value } );
							},
						} )
					),
					createElement(
						BaseControl,
						{ label: 'End time', help: 'Leave blank for midnight (00:00:00).' },
						createElement( 'input', {
							type: 'time',
							className: 'components-text-control__input',
							step: '1',
							value: ( attrs.end_time || '00:00:00' ).slice( 0, 8 ),
							onChange: function ( event ) {
								var value = event.target.value || '00:00:00';
								if ( value.length === 5 ) {
									value += ':00';
								}
								props.setAttributes( { end_time: value } );
							},
						} )
					)
				)
			)
		);
	}





	function TeamProfileEditor( props ) {
		var profile = props.profile;
		var nested = !! props.nested;
		var photoPreview = useMediaPreview( profile.photo );
		var wrapperClass = nested
			? 'rsmspark-cw-repeater-accordion__chip-fields'
			: 'rsmspark-cw-block-editor__repeater-item';

		return createElement(
			'div',
			{ className: wrapperClass },
			nested
				? null
				: createElement(
						'div',
						{ className: 'rsmspark-cw-block-editor__repeater-head' },
						createElement(
							'p',
							{ className: 'rsmspark-cw-block-editor__repeater-title' },
							t( 'profile', 'Profile' ) + ' ' + ( props.index + 1 )
						),
						createElement(
							Button,
							{
								variant: 'link',
								isDestructive: true,
								onClick: props.onRemove,
								disabled: props.disableRemove,
							},
							t( 'removeItem', 'Remove' )
						)
				  ),
			createElement( MediaImageControl, {
				label: 'Photo',
				value: profile.photo,
				previewUrl: photoPreview,
				onChange: function ( media ) {
					props.onChange( 'photo', parseInt( media.id, 10 ) || 0 );
				},
			} ),
			createElement( TextControl, {
				label: 'Name',
				value: profile.name || '',
				onChange: function ( value ) {
					props.onChange( 'name', value );
				},
			} ),
			createElement( TextControl, {
				label: 'Role',
				value: profile.role || '',
				onChange: function ( value ) {
					props.onChange( 'role', value );
				},
			} ),
			createElement( TextareaControl, {
				label: 'Quote',
				value: profile.quote || '',
				onChange: function ( value ) {
					props.onChange( 'quote', value );
				},
				rows: 3,
			} ),
			createElement( RichTextProfileField, {
				label: t( 'bioModal', 'Bio (modal)' ),
				profile: 'body',
				fieldKey: 'testimonial-bio-' + props.index,
				instanceId: props.instanceId || String( props.index ),
				value: profile.bio || '',
				help: t(
					'testimonialBioHelp',
					'Body profile: paragraphs and lists shown in the modal.'
				),
				onChange: function ( value ) {
					props.onChange( 'bio', value );
				},
			} )
		);
	}

	function TeamProfileAccordionItem( props ) {
		var profile = props.profile;
		var index = props.index;
		var photoPreview = useMediaPreview( profile.photo );
		var displayTitle =
			String( profile.name || '' ).trim() ||
			t( 'profile', 'Profile' ) + ' ' + ( index + 1 );

		return createElement(
			GenericRepeaterAccordionEditor,
			{
				indexLabel: t( 'profile', 'Profile' ) + ' ' + ( index + 1 ),
				displayTitle: displayTitle,
				meta: repeaterAccordionHeaderThumb( photoPreview ),
				isOpen: props.isOpen,
				onToggle: props.onToggle,
				disableRemove: props.disableRemove,
				onRemove: props.onRemove,
			},
			createElement( TeamProfileEditor, {
				nested: true,
				index: index,
				instanceId: props.instanceId,
				profile: profile,
				onChange: props.onChange,
			} )
		);
	}

	function TestimonialsTeamEdit( props ) {
		var attrs = props.attributes;
		var profiles = Array.isArray( attrs.profiles ) ? attrs.profiles.slice() : [];
		var source = attrs.source || 'manual';
		var isLibrary = source === 'library';
		var accordionOpen = useRepeaterAccordionOpenState();
		useSeedRepeaterDefaults(
			'rsm-content-widgets/testimonials-team',
			'profiles',
			attrs,
			props.setAttributes
		);

		function updateProfile( index, field, value ) {
			var next = profiles.slice();
			next[ index ] = Object.assign( {}, next[ index ], {} );
			next[ index ][ field ] = value;
			props.setAttributes( { profiles: next } );
		}

		function removeProfile( index ) {
			props.setAttributes( {
				profiles: profiles.filter( function ( _, i ) {
					return i !== index;
				} ),
			} );
		}

		function addProfile() {
			accordionOpen.setOpen( profiles.length, true );
			props.setAttributes( {
				profiles: profiles.concat( [
					{
						photo: 0,
						name: '',
						role: '',
						quote: '',
						bio: '',
					},
				] ),
			} );
		}

		var settings = blockInspectorSettings(
			'testimonials-team',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'layout', 'Layout' ), initialOpen: true },
					createElement( SelectControl, {
						label: t( 'layout', 'Layout' ),
						value: attrs.layout || 'grid',
						options: [
							{ label: 'Grid', value: 'grid' },
							{ label: 'Carousel', value: 'carousel' },
						],
						onChange: function ( value ) {
							props.setAttributes( { layout: value } );
						},
					} ),
					createElement( SelectControl, {
						label: t( 'testimonialSource', 'Data source' ),
						value: source,
						options: [
							{ label: t( 'testimonialSourceManual', 'Manual profiles' ), value: 'manual' },
							{
								label: t( 'testimonialSourceLibrary', 'Admin library' ),
								value: 'library',
							},
						],
						onChange: function ( value ) {
							props.setAttributes( { source: value } );
						},
					} )
				),
				isLibrary
					? createElement(
							PanelBody,
							{ title: t( 'testimonialLibrary', 'Library' ), initialOpen: true },
							createElement( RangeControl, {
								label: t( 'testimonialLibraryLimit', 'Limit' ),
								value: attrs.library_limit || 12,
								onChange: function ( value ) {
									props.setAttributes( { library_limit: value } );
								},
								min: 1,
								max: 24,
							} ),
							createElement( SelectControl, {
								label: t( 'testimonialLibraryOrderBy', 'Order by' ),
								value: attrs.library_orderby || 'date',
								options: [
									{ label: t( 'date', 'Date' ), value: 'date' },
									{ label: t( 'menuOrder', 'Menu order' ), value: 'menu_order' },
									{ label: t( 'title', 'Name' ), value: 'title' },
								],
								onChange: function ( value ) {
									props.setAttributes( { library_orderby: value } );
								},
							} ),
							createElement( SelectControl, {
								label: t( 'testimonialLibraryOrder', 'Order' ),
								value: attrs.library_order || 'DESC',
								options: [
									{ label: t( 'newestFirst', 'Newest first' ), value: 'DESC' },
									{ label: t( 'oldestFirst', 'Oldest first' ), value: 'ASC' },
								],
								onChange: function ( value ) {
									props.setAttributes( { library_order: value } );
								},
							} ),
							createElement(
								Notice,
								{ status: 'info', isDismissible: false },
								t(
									'testimonialLibraryHelp',
									'Manage entries under RSM → Testimonials. Published count: '
								) + ( config.testimonialPublishedCount || 0 )
							)
					  )
					: null,
			)
		, blockJsonContext( props, 'rsm-content-widgets/testimonials-team' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/testimonials-team',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( TextControl, {
						label: t( 'sectionTitle', 'Section title' ),
						value: attrs.title || '',
						onChange: function ( value ) {
							props.setAttributes( { title: value } );
						},
					} ),
					isLibrary
						? createElement(
								Notice,
								{ status: 'info', isDismissible: false },
								t(
									'testimonialLibraryCanvas',
									'Profiles load from the admin testimonial library on the front end.'
								)
						  )
						: createElement(
								'div',
								{
									className:
										'rsmspark-cw-block-editor__repeater-accordion-list',
								},
								profiles.map( function ( profile, index ) {
									return createElement( TeamProfileAccordionItem, {
										key: 'profile-' + index,
										index: index,
										instanceId: props.clientId,
										profile: profile,
										disableRemove: profiles.length <= 1,
										isOpen: accordionOpen.isOpen( index ),
										onToggle: function () {
											accordionOpen.toggleOpen( index );
										},
										onRemove: function () {
											removeProfile( index );
										},
										onChange: function ( field, value ) {
											updateProfile( index, field, value );
										},
									} );
								} )
						  ),
					! isLibrary
						? createElement(
								Button,
								{
									variant: 'secondary',
									onClick: addProfile,
								},
								t( 'addItem', 'Add profile' )
						  )
						: null
				)
			)
		);
	}















	function ImageEdit( props ) {
		var attrs = props.attributes;
		var imagePreview = useMediaPreview( attrs.image );
		var thumbPreview = attrs.image_src || imagePreview;
		var settings = blockInspectorSettings(
			'image',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'layout', 'Layout' ), initialOpen: true },
					createElement( RangeControl, {
						label: 'Image width (%)',
						value: attrs.image_width || 100,
						onChange: function ( value ) {
							props.setAttributes( { image_width: value } );
						},
						min: 10,
						max: 100,
					} ),
					createElement( RangeControl, {
						label: 'Max width (px, 0 = none)',
						value: attrs.max_width || 0,
						onChange: function ( value ) {
							props.setAttributes( { max_width: value } );
						},
						min: 0,
						max: 1600,
					} ),
					createElement( SelectControl, {
						label: 'Horizontal align',
						value: attrs.align_horizontal || 'center',
						options: [
							{ label: 'Left', value: 'left' },
							{ label: 'Center', value: 'center' },
							{ label: 'Right', value: 'right' },
						],
						onChange: function ( value ) {
							props.setAttributes( { align_horizontal: value } );
						},
					} ),
					createElement( SelectControl, {
						label: 'Vertical align',
						value: attrs.align_vertical || 'center',
						options: [
							{ label: 'Top', value: 'top' },
							{ label: 'Center', value: 'center' },
							{ label: 'Bottom', value: 'bottom' },
						],
						onChange: function ( value ) {
							props.setAttributes( { align_vertical: value } );
						},
					} ),
					createElement( SelectControl, {
						label: 'Focal point',
						value: attrs.focal_preset || 'center',
						options: [
							{ label: 'Center', value: 'center' },
							{ label: 'Top', value: 'top' },
							{ label: 'Bottom', value: 'bottom' },
							{ label: 'Left', value: 'left' },
							{ label: 'Right', value: 'right' },
							{ label: 'Custom', value: 'custom' },
						],
						onChange: function ( value ) {
							props.setAttributes( { focal_preset: value } );
						},
					} ),
					createElement( SelectControl, {
						label: 'Aspect ratio crop',
						value: attrs.aspect_ratio || 'auto',
						options: [
							{ label: 'Natural', value: 'auto' },
							{ label: '16:9', value: '16-9' },
							{ label: '4:3', value: '4-3' },
							{ label: '1:1', value: '1-1' },
							{ label: '3:4', value: '3-4' },
							{ label: '21:9', value: '21-9' },
						],
						onChange: function ( value ) {
							props.setAttributes( { aspect_ratio: value } );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: 'Design', initialOpen: false },
					createElement( SelectControl, {
						label: 'Corners',
						value: attrs.corners || 'rounded',
						options: [
							{ label: 'Square', value: 'square' },
							{ label: 'Rounded', value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { corners: value } );
						},
					} ),
					createElement( RangeControl, {
						label: 'Corner radius',
						value: attrs.corner_radius || 12,
						onChange: function ( value ) {
							props.setAttributes( { corner_radius: value } );
						},
						min: 0,
						max: 64,
					} ),
					createElement( ToggleControl, {
						label: 'Show border',
						checked: !! attrs.border_enable,
						onChange: function ( value ) {
							props.setAttributes( { border_enable: value } );
						},
					} )
				)
			)
		, blockJsonContext( props, 'rsm-content-widgets/image' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/image',
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( MediaImageControl, {
						label: t( 'image', 'Image' ),
						value: attrs.image,
						previewUrl: thumbPreview,
						onChange: function ( media ) {
							var id =
								media && media.id ? String( media.id ) : '';
							var url =
								media && media.url ? String( media.url ) : '';
							props.setAttributes( {
								image: parseInt( id, 10 ) || 0,
								image_src: url,
							} );
						},
					} ),
					createElement( TextControl, {
						label: 'Alt text',
						value: attrs.alt_text || '',
						onChange: function ( value ) {
							props.setAttributes( { alt_text: value } );
						},
					} ),
					createElement( TextControl, {
						label: 'Link URL',
						value: attrs.link || '',
						onChange: function ( value ) {
							props.setAttributes( { link: value } );
						},
					} ),
					createElement( ToggleControl, {
						label: t( 'imageLightbox', 'Open in lightbox' ),
						help: t(
							'imageLightboxHelp',
							'Click to view the full-size image in an overlay. Disabled when a link URL is set.'
						),
						checked: !! attrs.lightbox_enable && ! ( attrs.link || '' ).trim(),
						disabled: !!( attrs.link || '' ).trim(),
						onChange: function ( value ) {
							props.setAttributes( { lightbox_enable: value } );
						},
					} ),
					attrs.lightbox_enable && ! ( attrs.link || '' ).trim()
						? createElement( TextControl, {
								label: t( 'imageLightboxCaption', 'Lightbox caption' ),
								value: attrs.lightbox_caption || '',
								onChange: function ( value ) {
									props.setAttributes( { lightbox_caption: value } );
								},
						  } )
						: null
				)
			)
		);
	}

	var HEADLINE_FONT_OPTIONS = [
		{ label: 'Theme default', value: 'inherit' },
		{ label: 'System UI', value: 'system_ui' },
		{ label: 'Georgia / Times', value: 'georgia_serif' },
		{ label: 'Arial / Helvetica', value: 'arial_sans' },
		{ label: 'Verdana', value: 'verdana' },
		{ label: 'Tahoma', value: 'tahoma' },
		{ label: 'Trebuchet MS', value: 'trebuchet' },
		{ label: 'Courier New', value: 'courier' },
		{ label: 'Palatino', value: 'palatino' },
	];

	var ALIGN_OPTIONS = [
		{ label: 'Left', value: 'left' },
		{ label: 'Center', value: 'center' },
		{ label: 'Right', value: 'right' },
	];

	function headlineTypographyPanel( title, prefix, attrs, setAttributes ) {
		return createElement(
			PanelBody,
			{ title: title, initialOpen: false },
			createElement( SelectControl, {
				label: 'Font',
				value: attrs[ prefix + '_font' ] || 'inherit',
				options: HEADLINE_FONT_OPTIONS,
				onChange: function ( value ) {
					var next = {};
					next[ prefix + '_font' ] = value;
					setAttributes( next );
				},
			} ),
			createElement( RangeControl, {
				label: 'Font size (px, 0 = responsive default)',
				value: attrs[ prefix + '_font_size' ] || 0,
				onChange: function ( value ) {
					var next = {};
					next[ prefix + '_font_size' ] = value;
					setAttributes( next );
				},
				min: 0,
				max: 72,
			} ),
			createElement( BaseControl, { label: 'Text color' },
				createElement( ColorPalette, {
					value: attrs[ prefix + '_color' ] || '',
					onChange: function ( value ) {
						var next = {};
						next[ prefix + '_color' ] = value || '';
						setAttributes( next );
					},
					clearable: true,
				} )
			),
			createElement( SelectControl, {
				label: 'Alignment',
				value: attrs[ prefix + '_align' ] || 'center',
				options: ALIGN_OPTIONS,
				onChange: function ( value ) {
					var next = {};
					next[ prefix + '_align' ] = value;
					setAttributes( next );
				},
			} )
		);
	}

	function getClassicEditorApi() {
		if ( typeof window.wp === 'undefined' ) {
			return null;
		}

		if ( window.wp.oldEditor && typeof window.wp.oldEditor.initialize === 'function' ) {
			return window.wp.oldEditor;
		}

		if ( window.wp.editor && typeof window.wp.editor.initialize === 'function' ) {
			return window.wp.editor;
		}

		return null;
	}

	function waitForClassicEditor( callback, maxAttempts ) {
		var attempts = 0;
		var limit = maxAttempts || 100;

		function tryReady() {
			var editorApi = getClassicEditorApi();

			if ( window.tinymce && editorApi ) {
				callback( editorApi );
				return;
			}

			attempts += 1;
			if ( attempts < limit ) {
				window.setTimeout( tryReady, 100 );
			}
		}

		tryReady();
	}

	var richTextInlineFlushers = [];

	function registerRichTextInlineFlusher( flusher ) {
		richTextInlineFlushers.push( flusher );
		return function () {
			richTextInlineFlushers = richTextInlineFlushers.filter( function ( fn ) {
				return fn !== flusher;
			} );
		};
	}

	function flushRichTextInlineEditors() {
		richTextInlineFlushers.slice().forEach( function ( fn ) {
			try {
				fn();
			} catch ( e ) {
				// Ignore flush errors for individual fields.
			}
		} );
	}

	if ( wp.data && ! window.rsmCwRichTextSaveHooked ) {
		window.rsmCwRichTextSaveHooked = true;
		var wasSavingRichTextPost = false;
		var wasAutosavingRichTextPost = false;
		wp.data.subscribe( function () {
			var isSaving = false;
			var isAutosaving = false;
			try {
				var editorStore = wp.data.select( 'core/editor' );
				isSaving = !! editorStore.isSavingPost();
				isAutosaving =
					typeof editorStore.isAutosavingPost === 'function'
						? !! editorStore.isAutosavingPost()
						: false;
			} catch ( e ) {
				return;
			}
			if (
				( isSaving && ! wasSavingRichTextPost )
				|| ( isAutosaving && ! wasAutosavingRichTextPost )
			) {
				flushRichTextInlineEditors();
			}
			wasSavingRichTextPost = isSaving;
			wasAutosavingRichTextPost = isAutosaving;
		} );
	}

	function RichTextProfileField( props ) {
		var profile = props.profile || 'headline';
		var inline =
			props.layout === 'inline' ||
			( props.layout !== 'modal' && profile === 'body' );
		var openState = useState( false );
		var isOpen = openState[ 0 ];
		var setOpen = openState[ 1 ];
		var editorId =
			'rsmspark-rt-block-' +
			profile +
			'-' +
			props.fieldKey +
			'-' +
			String( props.instanceId || '0' );
		var draftState = useState( props.value || '' );
		var draft = draftState[ 0 ];
		var setDraft = draftState[ 1 ];
		var onChangeRef = useRef( props.onChange );
		var valueRef = useRef( props.value || '' );
		var editorGlobal = window.rsmRichTextEditor || {};
		var editorUi = editorGlobal.editorUi || {};
		var mediaRowEnabled = !! editorUi.mediaRow;
		var useRsmMedia = !! editorUi.useRsmMedia || shouldUseRsmCwMedia();
		var showMediaButtons = mediaRowEnabled || useRsmMedia;
		var shouldInit = inline || isOpen;

		onChangeRef.current = props.onChange;

		useEffect(
			function () {
				valueRef.current = props.value || '';
				if ( ! inline && ! isOpen ) {
					setDraft( props.value || '' );
				}
			},
			[ props.value, inline, isOpen ]
		);

		function emitChange( html ) {
			var next = html || '';
			if ( next === valueRef.current ) {
				return;
			}
			valueRef.current = next;
			if ( typeof onChangeRef.current === 'function' ) {
				onChangeRef.current( next );
			}
		}

		function tinymceSettings() {
			var profileInit =
				editorGlobal.tinymceInit && editorGlobal.tinymceInit[ profile ]
					? editorGlobal.tinymceInit[ profile ]
					: {};
			var defaults =
				profile === 'body'
					? {
							wpautop: true,
							forced_root_block: 'p',
							force_br_newlines: false,
							force_p_newlines: true,
							toolbar2: '',
							plugins: 'charmap,link,lists,wordpress,wplink,rsm_rich_text',
							rsm_rt_profile: 'body',
							body_class: 'rsmspark-rt-editor rsmspark-rt-root rsmspark-rt-profile-body',
							height: 260,
					  }
					: {
							wpautop: false,
							forced_root_block: false,
							force_br_newlines: true,
							toolbar2: '',
							plugins: 'charmap,link,lists,wordpress,wplink,rsm_rich_text',
							rsm_rt_profile: 'headline',
							body_class: 'rsmspark-rt-editor rsmspark-rt-root rsmspark-rt-profile-headline',
							height: 180,
					  };
			var settings = Object.assign( {}, defaults, profileInit );

			if ( editorGlobal.profiles ) {
				settings.rsm_rt_profiles = JSON.stringify( editorGlobal.profiles );
			}

			return settings;
		}

		function syncEditorContent() {
			var editorApi = getClassicEditorApi();
			var html = '';

			if ( editorApi && editorApi.getContent ) {
				html = editorApi.getContent( editorId ) || '';
			} else {
				var textarea = document.getElementById( editorId );
				html = textarea ? textarea.value : '';
			}

			emitChange( html );
		}

		function bindEditorChangeEvents() {
			var editor = window.tinymce && window.tinymce.get( editorId );
			if ( ! editor ) {
				return;
			}

			var sync = function () {
				emitChange( editor.getContent() || '' );
			};

			editor.on( 'change keyup undo redo SetContent', sync );
		}

		function bindTextareaEvents() {
			var textarea = document.getElementById( editorId );
			if ( ! textarea ) {
				return undefined;
			}

			var sync = function () {
				emitChange( textarea.value || '' );
			};

			textarea.addEventListener( 'input', sync );
			textarea.addEventListener( 'blur', sync );

			return function () {
				textarea.removeEventListener( 'input', sync );
				textarea.removeEventListener( 'blur', sync );
			};
		}

		function bindContentChangedBridge( changedEditorId, onChange ) {
			if ( ! window.jQuery ) {
				return undefined;
			}

			var handler = function ( event, id, html ) {
				if ( id !== changedEditorId ) {
					return;
				}
				if ( typeof html === 'string' ) {
					emitChange( html );
				}
			};

			window.jQuery( document ).on( 'rsmspark-rt-editor-content-changed', handler );

			return function () {
				window.jQuery( document ).off( 'rsmspark-rt-editor-content-changed', handler );
			};
		}

		function injectRsmMediaButton( targetEditorId ) {
			if ( ! useRsmMedia ) {
				return;
			}

			var textarea = document.getElementById( targetEditorId );
			if ( ! textarea ) {
				return;
			}

			var wrap = textarea.closest( '.rsmspark-rt-block-editor-wrap' );
			if ( ! wrap || wrap.querySelector( '.rsmspark-rt-insert-media' ) ) {
				return;
			}

			var tools = wrap.querySelector( '.wp-editor-tools' );
			if ( ! tools ) {
				return;
			}

			var mediaButtons = tools.querySelector( '.wp-media-buttons' );
			if ( ! mediaButtons ) {
				mediaButtons = document.createElement( 'div' );
				mediaButtons.className = 'wp-media-buttons';
				tools.insertBefore( mediaButtons, tools.firstChild );
			}

			var button = document.createElement( 'button' );
			button.type = 'button';
			button.className = 'button rsmspark-rt-insert-media';
			button.setAttribute( 'data-editor-id', targetEditorId );
			button.textContent =
				( editorGlobal.profiles &&
					editorGlobal.profiles[ profile ] &&
					editorGlobal.profiles[ profile ].i18n &&
					editorGlobal.profiles[ profile ].i18n.insertMedia ) ||
				t( 'insertRsmMedia', 'Add Media' );
			mediaButtons.appendChild( button );
		}

		useEffect(
			function () {
				if ( ! shouldInit ) {
					return undefined;
				}

				var cancelled = false;
				var initTimer = null;
				var removeBridge = null;
				var removeTextarea = null;
				var unregisterFlush = null;

				if ( inline || isOpen ) {
					unregisterFlush = registerRichTextInlineFlusher( syncEditorContent );
				}

				waitForClassicEditor( function ( editorApi ) {
					if ( cancelled ) {
						return;
					}

					var textarea = document.getElementById( editorId );
					if ( ! textarea ) {
						return;
					}

					editorApi.initialize( editorId, {
						tinymce: tinymceSettings(),
						quicktags: inline,
						mediaButtons: showMediaButtons,
					} );

					if ( editorApi.setContent ) {
						editorApi.setContent( editorId, props.value || '' );
					}

					initTimer = window.setTimeout( function () {
						if ( cancelled ) {
							return;
						}
						injectRsmMediaButton( editorId );
						bindEditorChangeEvents();
						removeTextarea = bindTextareaEvents();
						removeBridge = bindContentChangedBridge(
							editorId,
							emitChange
						);
						if ( window.jQuery ) {
							window.jQuery( document ).trigger( 'rsmspark-rt-block-editor-init', [
								editorId,
							] );
						}
					}, 150 );
				} );

				return function () {
					cancelled = true;
					if ( initTimer ) {
						window.clearTimeout( initTimer );
					}
					if ( typeof removeBridge === 'function' ) {
						removeBridge();
					}
					if ( typeof removeTextarea === 'function' ) {
						removeTextarea();
					}
					if ( typeof unregisterFlush === 'function' ) {
						unregisterFlush();
					}
					if ( inline ) {
						syncEditorContent();
					}
					var editorApi = getClassicEditorApi();
					if ( editorApi && editorApi.remove ) {
						editorApi.remove( editorId );
					}
				};
			},
			[ shouldInit, editorId, profile, inline, isOpen ]
		);

		useEffect(
			function () {
				if ( ! shouldInit ) {
					return;
				}

				var editorApi = getClassicEditorApi();
				if ( editorApi && editorApi.setContent ) {
					var current = editorApi.getContent
						? editorApi.getContent( editorId ) || ''
						: '';
					if ( current !== ( props.value || '' ) ) {
						editorApi.setContent( editorId, props.value || '' );
					}
					return;
				}

				var textarea = document.getElementById( editorId );
				if ( textarea && textarea.value !== ( props.value || '' ) ) {
					textarea.value = props.value || '';
				}
			},
			[ props.value, shouldInit, editorId ]
		);

		function closeModal( save ) {
			if ( save ) {
				syncEditorContent();
				setDraft( valueRef.current || '' );
			}
			var editorApi = getClassicEditorApi();
			if ( editorApi && editorApi.remove ) {
				editorApi.remove( editorId );
			}
			setOpen( false );
		}

		function editorWrap( contentValue ) {
			return createElement(
				'div',
				{ className: 'rsmspark-rt-block-editor-wrap' },
				createElement( 'textarea', {
					id: editorId,
					className:
						'widefat rsmspark-rt-editor wp-editor-area wp-exclude-emoji rsmspark-rt-profile-' +
						profile,
					defaultValue: contentValue || '',
				} )
			);
		}

		if ( inline ) {
			return createElement(
				BaseControl,
				{
					label: props.label,
					help:
						props.help ||
						'Body profile: paragraphs, lists, size/color tokens.',
				},
				editorWrap( props.value || '' )
			);
		}

		return createElement(
			BaseControl,
			{
				label: props.label,
				help:
					props.help ||
					'Open the rich editor for size, color, and gradient tokens.',
			},
			draft || richTextHasContent( props.value )
				? createElement( 'div', {
						className:
							'rsmspark-rt-block-preview rsmspark-rt-root rsmspark-rt-profile-' + profile,
						dangerouslySetInnerHTML: {
							__html: draft || props.value || '',
						},
				  } )
				: null,
			createElement(
				Button,
				{
					variant: 'secondary',
					onClick: function () {
						setDraft( props.value || '' );
						setOpen( true );
					},
				},
				props.buttonLabel || 'Edit rich text'
			),
			isOpen
				? createElement(
						Modal,
						{
							title: props.label,
							onRequestClose: function () {
								closeModal( false );
							},
							className: 'rsmspark-rt-block-modal',
						},
						editorWrap( draft || '' ),
						createElement(
							'div',
							{ className: 'rsmspark-rt-block-modal__actions' },
							createElement(
								Button,
								{
									variant: 'secondary',
									onClick: function () {
										closeModal( false );
									},
								},
								'Cancel'
							),
							createElement(
								Button,
								{
									variant: 'primary',
									onClick: function () {
										closeModal( true );
									},
								},
								'Apply'
							)
						)
				  )
				: null
		);
	}

	function RichLineField( props ) {
		return createElement(
			RichTextProfileField,
			Object.assign( {}, props, {
				profile: 'headline',
				layout: 'modal',
				instanceId:
					props.instanceId +
					( props.fieldKey ? '-' + props.fieldKey : '' ),
			} )
		);
	}

	function HeadlineEdit( props ) {
		var attrs = props.attributes;
		var blockName = resolveCwBlockName( props, 'rsm-content-widgets/headline' );

		useSeedScalarDefaults(
			blockName,
			[ 'overline', 'headline', 'subheadline', 'headline_tag' ],
			attrs,
			props.setAttributes
		);

		var settings = blockInspectorSettings(
			'headline',
			createElement(
				Fragment,
				null,
				headlineTypographyPanel( 'Overline', 'overline', attrs, props.setAttributes ),
				headlineTypographyPanel( 'Headline', 'headline', attrs, props.setAttributes ),
				headlineTypographyPanel( 'Subheadline', 'subheadline', attrs, props.setAttributes ),
				createElement(
					PanelBody,
					{ title: 'Container', initialOpen: false },
					createElement( RangeControl, {
						label: 'Width (%)',
						value: attrs.width_pct || 100,
						onChange: function ( value ) {
							props.setAttributes( { width_pct: value } );
						},
						min: 10,
						max: 100,
					} ),
					createElement( RangeControl, {
						label: 'Max width (px, 0 = none)',
						value: attrs.max_width || 0,
						onChange: function ( value ) {
							props.setAttributes( { max_width: value } );
						},
						min: 0,
						max: 1600,
					} ),
					createElement( BaseControl, { label: 'Background color' },
						createElement( ColorPalette, {
							value: attrs.background_color || '',
							onChange: function ( value ) {
								props.setAttributes( { background_color: value || '' } );
							},
							clearable: true,
						} )
					),
					createElement( SelectControl, {
						label: 'Corners',
						value: attrs.corners || 'square',
						options: [
							{ label: 'Square', value: 'square' },
							{ label: 'Rounded', value: 'rounded' },
						],
						onChange: function ( value ) {
							props.setAttributes( { corners: value } );
						},
					} ),
					createElement( RangeControl, {
						label: 'Corner radius',
						value: attrs.corner_radius || 12,
						onChange: function ( value ) {
							props.setAttributes( { corner_radius: value } );
						},
						min: 0,
						max: 64,
					} ),
					createElement( ToggleControl, {
						label: 'Show border',
						checked: !! attrs.border_enable,
						onChange: function ( value ) {
							props.setAttributes( { border_enable: value } );
						},
					} ),
					createElement( RangeControl, {
						label: 'Border thickness',
						value: attrs.border_width || 1,
						onChange: function ( value ) {
							props.setAttributes( { border_width: value } );
						},
						min: 1,
						max: 12,
					} ),
					createElement( BaseControl, { label: 'Border color' },
						createElement( ColorPalette, {
							value: attrs.border_color || '#e2e8f0',
							onChange: function ( value ) {
								props.setAttributes( { border_color: value || '#e2e8f0' } );
							},
						} )
					),
					createElement( RangeControl, {
						label: 'Border opacity',
						value: attrs.border_opacity || 100,
						onChange: function ( value ) {
							props.setAttributes( { border_opacity: value } );
						},
						min: 0,
						max: 100,
					} )
				)
			)
		, blockJsonContext( props, blockName ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: blockName,
					attributes: attrs,
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					createElement( RichLineField, {
						label: 'Overline (eyebrow)',
						fieldKey: 'overline',
						instanceId: props.clientId,
						value: attrs.overline || '',
						help: 'Eyebrow kicker — try Eyebrow size and Muted or Accent color.',
						onChange: function ( value ) {
							props.setAttributes( { overline: value } );
						},
					} ),
					createElement( RichLineField, {
						label: 'Headline',
						fieldKey: 'headline',
						instanceId: props.clientId,
						value: attrs.headline || '',
						help: 'Use Display, Hero, or Jumbo size with Accent or Gradient on key words.',
						onChange: function ( value ) {
							props.setAttributes( { headline: value } );
						},
					} ),
					createElement( RichLineField, {
						label: 'Subheadline',
						fieldKey: 'subheadline',
						instanceId: props.clientId,
						value: attrs.subheadline || '',
						help: 'Supporting copy — Lead or Body size works well.',
						onChange: function ( value ) {
							props.setAttributes( { subheadline: value } );
						},
					} ),
					createElement( SelectControl, {
						label: 'Headline HTML tag',
						value: attrs.headline_tag || 'h2',
						options: [
							{ label: 'H1', value: 'h1' },
							{ label: 'H2', value: 'h2' },
							{ label: 'H3', value: 'h3' },
							{ label: 'H4', value: 'h4' },
							{ label: 'H5', value: 'h5' },
							{ label: 'H6', value: 'h6' },
						],
						onChange: function ( value ) {
							props.setAttributes( { headline_tag: value } );
						},
					} )
				)
			)
		);
	}

	function ButtonsEdit( props ) {
		var attrs = props.attributes;
		var setAttributes = props.setAttributes;
		var mode = attrs.source_mode || 'saved';
		var buttonId = parseInt( attrs.id, 10 ) || 0;
		var savedState = useState( [
			{ label: '— Select a saved button —', value: '0' },
		] );
		var savedOptions = savedState[ 0 ];
		var setSavedOptions = savedState[ 1 ];

		useEffect( function () {
			if ( ! apiFetch ) {
				return;
			}

			apiFetch( { path: '/rsm-content-widgets/v1/buttons' } )
				.then( function ( items ) {
					var options = [
						{ label: '— Select a saved button —', value: '0' },
					];

					( items || [] ).forEach( function ( item ) {
						options.push( {
							label:
								( item.title || 'Button' ) +
								' (#' +
								item.id +
								')',
							value: String( item.id ),
						} );
					} );

					setSavedOptions( options );
				} )
				.catch( function () {} );
		}, [] );

		function savedButtonControl( label ) {
			return createElement( SelectControl, {
				label: label || 'Saved button',
				value: String( buttonId ),
				options: savedOptions,
				onChange: function ( value ) {
					setAttributes( { id: parseInt( value, 10 ) || 0 } );
				},
			} );
		}

		var settings = blockInspectorSettings(
			'buttons',
			createElement(
				PanelBody,
				{ title: t( 'content', 'Content' ), initialOpen: true },
				createElement( SelectControl, {
					label: 'Button source',
					value: mode,
					options: [
						{ label: 'Saved button', value: 'saved' },
						{ label: 'Custom inline', value: 'custom' },
					],
					onChange: function ( value ) {
						setAttributes( { source_mode: value } );
					},
				} ),
				mode === 'saved' ? savedButtonControl( 'Saved button' ) : null,
				mode === 'custom'
					? createElement(
							Fragment,
							null,
							createElement( TextControl, {
								label: 'Text line 1',
								value: attrs.text_line_1 || '',
								onChange: function ( value ) {
									props.setAttributes( { text_line_1: value } );
								},
							} ),
							createElement( TextControl, {
								label: 'Text line 2',
								value: attrs.text_line_2 || '',
								onChange: function ( value ) {
									props.setAttributes( { text_line_2: value } );
								},
							} ),
							createElement( TextControl, {
								label: 'Link URL',
								value: attrs.link_url || '',
								onChange: function ( value ) {
									props.setAttributes( { link_url: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Shape',
								value: attrs.shape || 'rounded',
								options: [
									{ label: 'Rectangle', value: 'rectangle' },
									{ label: 'Rounded', value: 'rounded' },
									{ label: 'Pill', value: 'pill' },
								],
								onChange: function ( value ) {
									props.setAttributes( { shape: value } );
								},
							} ),
							createElement( SelectControl, {
								label: 'Size',
								value: attrs.size || 'medium',
								options: [
									{ label: 'Small', value: 'small' },
									{ label: 'Medium', value: 'medium' },
									{ label: 'Large', value: 'large' },
								],
								onChange: function ( value ) {
									props.setAttributes( { size: value } );
								},
							} )
					  )
					: null
			)
		, blockJsonContext( props, 'rsm-content-widgets/buttons' ) );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/buttons',
					attributes: attrs,
					previewDisabled: buttonId <= 0 && mode !== 'custom',
					previewDisabledMessage: 'Select a saved button above to see the preview.',
				},
				settings.canvas,
				createElement(
					'div',
					{ className: 'rsmspark-cw-block-editor' },
					mode === 'saved'
						? createElement(
								'div',
								{ className: 'rsmspark-cw-block-editor__picker' },
								createElement(
									'p',
									{ className: 'description' },
									'Choose a saved button from your library:'
								),
								savedButtonControl( 'Saved button' ),
								buttonId <= 0 && savedOptions.length <= 1
									? createElement(
											'p',
											{ className: 'description' },
											'No saved buttons yet. Create one under RSM → Buttons.'
									  )
									: null
						  )
						: null
				)
			)
		);
	}


	function footerPageTypeKeys() {
		return Object.keys( config.footerPageTypes || {} );
	}

	function footerColumnHasType( column, field, slug ) {
		var list =
			column && Array.isArray( column[ field ] ) ? column[ field ] : [];
		return list.indexOf( slug ) !== -1;
	}

	function footerPostTypeKeys() {
		return Object.keys( config.footerPostTypes || {} );
	}

	function footerInstanceHasType( attrs, field, slug ) {
		var list = Array.isArray( attrs[ field ] ) ? attrs[ field ] : [];
		return list.indexOf( slug ) !== -1;
	}

	function footerInstanceVisibilityField( attrs, field, optionsKey, label, help, setAttributes ) {
		var options = config[ optionsKey ] || {};
		return createElement(
			BaseControl,
			{
				label: label,
				help: help,
				className: 'rsmspark-cw-footer-visibility',
			},
			Object.keys( options ).map( function ( slug ) {
				return createElement( ToggleControl, {
					key: field + '-' + slug,
					label: options[ slug ] || slug,
					checked: footerInstanceHasType( attrs, field, slug ),
					onChange: function ( checked ) {
						var list = Array.isArray( attrs[ field ] )
							? attrs[ field ].slice()
							: [];
						if ( checked ) {
							if ( list.indexOf( slug ) === -1 ) {
								list.push( slug );
							}
						} else {
							list = list.filter( function ( item ) {
								return item !== slug;
							} );
						}
						var patch = {};
						patch[ field ] = list;
						setAttributes( patch );
					},
				} );
			} )
		);
	}

	function footerColumnVisibilityField( columns, index, field, label, help, setAttributes ) {
		var column = columns[ index ] || {};
		return createElement(
			BaseControl,
			{
				label: label,
				help: help,
				className: 'rsmspark-cw-footer-visibility',
			},
			footerPageTypeKeys().map( function ( slug ) {
				return createElement( ToggleControl, {
					key: field + '-' + slug,
					label: ( config.footerPageTypes || {} )[ slug ] || slug,
					checked: footerColumnHasType( column, field, slug ),
					onChange: function ( checked ) {
						var next = columns.slice();
						var row = Object.assign( {}, next[ index ] || {} );
						var list = Array.isArray( row[ field ] )
							? row[ field ].slice()
							: [];
						if ( checked ) {
							if ( list.indexOf( slug ) === -1 ) {
								list.push( slug );
							}
						} else {
							list = list.filter( function ( item ) {
								return item !== slug;
							} );
						}
						row[ field ] = list;
						next[ index ] = row;
						setAttributes( { columns: next } );
					},
				} );
			} )
		);
	}

	function FooterEdit( props ) {
		var attrs = props.attributes;
		var columns = Array.isArray( attrs.columns ) ? attrs.columns : [];
		var design = attrs.design || {};
		var isSticky = design.placement === 'sticky_bar';
		var settings = blockInspectorSettings(
			'footer',
			createElement(
				Fragment,
				null,
				createElement(
					PanelBody,
					{ title: t( 'footerBrand', 'Brand' ), initialOpen: true },
					createElement( TextControl, {
						label: t( 'footerBrandName', 'Brand name' ),
						value: ( attrs.brand && attrs.brand.name ) || '',
						onChange: function ( value ) {
							props.setAttributes( {
								brand: Object.assign( {}, attrs.brand || {}, { name: value } ),
							} );
						},
					} ),
					createElement( TextareaControl, {
						label: t( 'footerBrandDescription', 'Brand description' ),
						value: ( attrs.brand && attrs.brand.description ) || '',
						onChange: function ( value ) {
							props.setAttributes( {
								brand: Object.assign( {}, attrs.brand || {}, { description: value } ),
							} );
						},
					} ),
					createElement( TextControl, {
						label: t( 'footerCopyright', 'Copyright' ),
						value: attrs.copyright || '',
						onChange: function ( value ) {
							props.setAttributes( { copyright: value } );
						},
					} )
				),
				createElement(
					PanelBody,
					{
						title: t( 'footerRegionVisibility', 'Region & visibility' ),
						initialOpen: false,
					},
					createElement( TextControl, {
						label: t( 'footerRegion', 'Region slug' ),
						help: t(
							'footerRegionHelp',
							'Optional label for admin reference (e.g. default, shop). Visibility rules below control where this footer appears.'
						),
						value: attrs.region || 'default',
						onChange: function ( value ) {
							props.setAttributes( { region: value } );
						},
					} ),
					footerInstanceVisibilityField(
						attrs,
						'show_on',
						'footerPageTypes',
						t( 'footerInstanceShowOn', 'Show footer on page types' ),
						t(
							'footerShowOnHelp',
							'Leave all off to show everywhere (unless hidden below).'
						),
						props.setAttributes
					),
					footerInstanceVisibilityField(
						attrs,
						'hide_on',
						'footerPageTypes',
						t( 'footerInstanceHideOn', 'Hide footer on page types' ),
						t(
							'footerHideOnHelp',
							'Hide this footer when the current page matches any selected type.'
						),
						props.setAttributes
					),
					footerInstanceVisibilityField(
						attrs,
						'show_on_post_types',
						'footerPostTypes',
						t( 'footerShowOnPostTypes', 'Show footer on post types' ),
						t(
							'footerShowOnPostTypesHelp',
							'Applies on single posts/pages of the selected types.'
						),
						props.setAttributes
					),
					footerInstanceVisibilityField(
						attrs,
						'hide_on_post_types',
						'footerPostTypes',
						t( 'footerHideOnPostTypes', 'Hide footer on post types' ),
						'',
						props.setAttributes
					)
				),
				createElement(
					PanelBody,
					{ title: t( 'design', 'Design' ), initialOpen: false },
					createElement( RangeControl, {
						label: t( 'footerBodyTextScale', 'Body text scale (%)' ),
						help: t(
							'footerBodyTextScaleHelp',
							'Scales links, descriptions, and legal text. Column titles and brand name keep their accent sizes.'
						),
						value: design.text_scale || 85,
						onChange: function ( value ) {
							props.setAttributes( {
								design: Object.assign( {}, design, {
									text_scale: value,
								} ),
							} );
						},
						min: 50,
						max: 135,
					} ),
					createElement( SelectControl, {
						label: t( 'footerPlacement', 'Placement' ),
						value: design.placement || 'static',
						options: [
							{
								label: t( 'footerPlacementStatic', 'Static (default)' ),
								value: 'static',
							},
							{
								label: t(
									'footerPlacementSticky',
									'Sticky bar (fixed to bottom)'
								),
								value: 'sticky_bar',
							},
						],
						onChange: function ( value ) {
							props.setAttributes( {
								design: Object.assign( {}, design, {
									placement: value,
								} ),
							} );
						},
					} ),
					isSticky
						? createElement( SelectControl, {
								label: t( 'footerRevealMode', 'Sticky reveal' ),
								value: design.reveal_mode || 'always',
								options: [
									{
										label: t( 'footerRevealAlways', 'Always visible' ),
										value: 'always',
									},
									{
										label: t(
											'footerRevealScrollUp',
											'Reveal on scroll up (hide on scroll down)'
										),
										value: 'scroll_up',
									},
									{
										label: t(
											'footerRevealScrollDown',
											'Hide on scroll up (reveal on scroll down)'
										),
										value: 'scroll_down',
									},
								],
								onChange: function ( value ) {
									props.setAttributes( {
										design: Object.assign( {}, design, {
											reveal_mode: value,
										} ),
									} );
								},
						  } )
						: null,
					isSticky
						? createElement( ToggleControl, {
								label: t( 'footerStickyCompact', 'Compact sticky bar' ),
								checked: !! design.sticky_compact,
								onChange: function ( value ) {
									props.setAttributes( {
										design: Object.assign( {}, design, {
											sticky_compact: value,
										} ),
									} );
								},
						  } )
						: null,
					createElement( ToggleControl, {
						label: t( 'footerBackToTop', 'Back to top button' ),
						checked: !! design.back_to_top,
						onChange: function ( value ) {
							props.setAttributes( {
								design: Object.assign( {}, design, {
									back_to_top: value,
								} ),
							} );
						},
					} )
				),
				createElement(
					PanelBody,
					{ title: t( 'footerColumns', 'Columns' ), initialOpen: false },
					columns.map( function ( column, index ) {
						return createElement(
							'div',
							{
								key: 'footer-col-' + index,
								className: 'rsmspark-cw-block-editor__repeater-item',
							},
							createElement(
								'p',
								{
									className:
										'rsmspark-cw-block-editor__repeater-title',
								},
								( column.title || t( 'footerColumn', 'Column' ) ) +
									' ' +
									( index + 1 )
							),
							footerColumnVisibilityField(
								columns,
								index,
								'show_on',
								t( 'footerShowOn', 'Show on page types' ),
								t(
									'footerShowOnHelp',
									'Leave all off to show everywhere (unless hidden below).'
								),
								props.setAttributes
							),
							footerColumnVisibilityField(
								columns,
								index,
								'hide_on',
								t( 'footerHideOn', 'Hide on page types' ),
								t(
									'footerHideOnHelp',
									'Hide this column when the current page matches any selected type.'
								),
								props.setAttributes
							)
						);
					} )
				),
				createElement(
					'p',
					{ className: 'description' },
					t(
						'footerEditorNote',
						'Use the SiteOrigin widget for full column and link editing. Visibility rules apply on the front end; the block preview shows all columns.'
					)
				)
			)
		, {
			blockName: 'rsm-content-widgets/footer',
			clientId: props.clientId,
			attributes: attrs,
			setAttributes: props.setAttributes,
			buildExport: function ( footerAttrs ) {
				return buildFooterExportPayload(
					footerAttrs,
					( footerAttrs.brand && footerAttrs.brand.name ) || 'Footer'
				);
			},
			parseImport: parseFooterImportPayload,
			mergeImport: function ( payload, current ) {
				return mergeFooterImportConfig(
					payload.config || {},
					current
				);
			},
		} );

		return createElement(
			Fragment,
			null,
			settings.inspector,
			createElement(
				BlockEditLayout,
				{
					clientId: props.clientId,
					blockName: 'rsm-content-widgets/footer',
					attributes: attrs,
				},
				settings.canvas
			)
		);
	}

	var editComponents = {
		'rsm-content-widgets/hero': HeroEdit,
		'rsm-content-widgets/tabs': TabsEdit,
		'rsm-content-widgets/accordion': AccordionEdit,
		'rsm-content-widgets/features': FeaturesEdit,
		'rsm-content-widgets/icon-box-grid': IconBoxGridEdit,
		'rsm-content-widgets/logo-grid': LogoGridEdit,
		'rsm-content-widgets/stats-counter': StatsCounterEdit,
		'rsm-content-widgets/event-countdown': EventCountdownEdit,
		'rsm-content-widgets/testimonials-team': TestimonialsTeamEdit,
		'rsm-content-widgets/image': ImageEdit,
		'rsm-content-widgets/headline': HeadlineEdit,
		'rsmspark-cw-compat/headline': HeadlineEdit,
		'rsm-content-widgets/footer': FooterEdit,
		'rsm-content-widgets/buttons': ButtonsEdit,
	};

	function resolveBlockEdit( blockName, Edit, serverBlock, externalEditor ) {
		if ( Edit ) {
			return wrapEditForInserterPreview( blockName, Edit );
		}

		if ( externalEditor ) {
			if ( serverBlock && serverBlock.edit ) {
				return serverBlock.edit;
			}
			return null;
		}

		return function () {
			return createElement(
				'p',
				null,
				'Editor UI not available for this block.'
			);
		};
	}

	blocks.forEach( function ( block ) {
		if ( block.external_editor ) {
			var externalBlock = wp.blocks.getBlockType( block.name );
			if ( externalBlock ) {
				registerBlockType( block.name, {
					example: blockExampleFor( block.name, block ),
					icon: rsmBlockIcon( block.icon || 'screenoptions' ),
				} );
			}
			return;
		}

		var Edit = editComponents[ block.name ];
		var serverBlock = wp.blocks.getBlockType( block.name );
		var externalEditor = !! block.external_editor;
		var resolvedEdit = resolveBlockEdit(
			block.name,
			Edit,
			serverBlock,
			externalEditor
		);
		var settings = {
			apiVersion: 3,
			title: block.title,
			description: block.description,
			category: block.category,
			keywords: block.keywords,
			supports: block.supports,
			example: blockExampleFor( block.name, block ),
			save: function () {
				return null;
			},
		};

		if ( resolvedEdit ) {
			settings.edit = resolvedEdit;
		}

		if ( blockAttributes[ block.name ] ) {
			var mergedAttrs = blockAttributes[ block.name ];
			if ( serverBlock && serverBlock.attributes ) {
				mergedAttrs = Object.assign(
					{},
					serverBlock.attributes,
					blockAttributes[ block.name ]
				);
			}
			settings.attributes = mergedAttrs;
		}

		settings.icon = rsmBlockIcon( block.icon || 'screenoptions' );

		// Bootstrap may register preview-only blocks first; replace with full UI
		// but always pass complete metadata (partial register → unsupported block).
		if ( serverBlock && resolvedEdit && wp.blocks.unregisterBlockType ) {
			wp.blocks.unregisterBlockType( block.name );
		}

		registerBlockType( block.name, settings );
	} );

	blocks.forEach( function ( block ) {
		var legacyName = block.legacy_name;
		if ( ! legacyName || legacyName === block.name ) {
			return;
		}

		var Edit = editComponents[ block.name ];
		var legacyServerBlock = wp.blocks.getBlockType( legacyName );
		var legacySupports = Object.assign( {}, block.supports || {} );
		// Keep rsmspark-cw-compat/* registered for existing content; hide from inserter
		// unless Advanced → Experimental blocks is on.
		if ( ! config.experimentalBlocks ) {
			legacySupports.inserter = false;
		}
		var legacySettings = {
			apiVersion: 3,
			title: block.title,
			description: block.description,
			category: block.category,
			keywords: block.keywords,
			supports: legacySupports,
			example: blockExampleFor( legacyName, block ),
			edit: Edit
				? wrapEditForInserterPreview( legacyName, Edit )
				: function () {
						return createElement(
							'p',
							null,
							'Editor UI not available for this block.'
						);
				  },
			save: function () {
				return null;
			},
		};

		if ( blockAttributes[ legacyName ] ) {
			legacySettings.attributes = blockAttributes[ legacyName ];
		}

		legacySettings.icon = rsmBlockIcon( block.icon || 'screenoptions' );

		if ( legacyServerBlock && Edit && wp.blocks.unregisterBlockType ) {
			wp.blocks.unregisterBlockType( legacyName );
		}

		registerBlockType( legacyName, legacySettings );
	} );

	function isHiddenSowbDuplicate( blockName ) {
		var hide = config.hideSowbBlocks;
		return hide && hide.length && hide.indexOf( blockName ) !== -1;
	}

	function suppressSowbInserterSettings( settings, blockName ) {
		if ( ! isHiddenSowbDuplicate( blockName ) ) {
			return settings;
		}

		var next = Object.assign( {}, settings );
		next.supports = Object.assign( {}, next.supports || {}, { inserter: false } );
		return next;
	}

	function patchRegisteredSowbDuplicates() {
		var hide = config.hideSowbBlocks;
		if (
			! hide ||
			! hide.length ||
			! wp.blocks.getBlockType ||
			! wp.blocks.unregisterBlockType ||
			! wp.blocks.registerBlockType
		) {
			return;
		}

		hide.forEach( function ( blockName ) {
			var blockType = wp.blocks.getBlockType( blockName );
			if ( ! blockType ) {
				return;
			}
			if ( blockType.supports && blockType.supports.inserter === false ) {
				return;
			}

			wp.blocks.unregisterBlockType( blockName );
			wp.blocks.registerBlockType(
				blockName,
				Object.assign( {}, blockType, {
					supports: Object.assign( {}, blockType.supports || {}, {
						inserter: false,
					} ),
				} )
			);
		} );
	}

	if ( wp.hooks && wp.hooks.addFilter ) {
		wp.hooks.addFilter(
			'blocks.registerBlockType',
			'rsmspark-cw/hide-sowb-widget-inserter',
			suppressSowbInserterSettings,
			20
		);

		wp.hooks.addFilter(
			'editor.BlockEdit',
			'rsmspark-cw/sowb-native-ui',
			function ( BlockEdit ) {
				var sowbNativeBridges = getSowbNativeBridges();

				return function ( props ) {
					var NativeEdit = sowbNativeBridges[ props.name ];
					if ( ! NativeEdit ) {
						return createElement( BlockEdit, props );
					}

					return createElement( NativeEdit, props );
				};
			},
			20
		);

		wp.hooks.addFilter(
			'editor.BlockEdit',
			'rsmspark-cw/inserter-output-preview',
			function ( BlockEdit ) {
				return function ( props ) {
					if (
						! props.name ||
						props.name.indexOf( 'rsm-content-widgets/' ) !== 0
					) {
						return createElement( BlockEdit, props );
					}

					var context =
						typeof useBlockEditContext === 'function'
							? useBlockEditContext()
							: {};

					if ( isInserterHoverPreview( context, props ) ) {
						return inserterOutputPreview(
							props.name,
							props.attributes
						);
					}

					return createElement( BlockEdit, props );
				};
			},
			5
		);
	}

	patchRegisteredSowbDuplicates();
	getSowbNativeBridges();
	if ( wp.domReady ) {
		wp.domReady( patchRegisteredSowbDuplicates );
	}
	[ 0, 100, 500, 1500 ].forEach( function ( ms ) {
		window.setTimeout( patchRegisteredSowbDuplicates, ms );
	} );

	var layoutAttributeSchema = {
		rsmBand: { type: 'string', default: '' },
		rsmBandBg: { type: 'string', default: '' },
		rsmBandPadTop: { type: 'number', default: 0 },
		rsmBandPadBottom: { type: 'number', default: 0 },
		rsmSpacingTop: { type: 'number', default: 0 },
		rsmSpacingBottom: { type: 'number', default: 0 },
	};

	var embedAttributeSchema = {
		rsmEmbedWidth: { type: 'number', default: 100 },
		rsmEmbedAlign: { type: 'string', default: 'center' },
	};

	wp.hooks.addFilter(
		'blocks.registerBlockType',
		'rsmspark-cw/layout-band-attributes',
		function ( settings, blockName ) {
			if (
				typeof blockName !== 'string' ||
				( blockName.indexOf( 'rsm-content-widgets/' ) !== 0 &&
					blockName.indexOf( 'rsmspark-cw-compat/' ) !== 0 )
			) {
				return settings;
			}

			settings.attributes = Object.assign(
				{},
				settings.attributes || {},
				layoutAttributeSchema,
				embedAttributeSchema
			);

			return settings;
		}
	);

	function isContentWidgetsBlockName( blockName ) {
		return (
			typeof blockName === 'string' &&
			( blockName.indexOf( 'rsm-content-widgets/' ) === 0 ||
				blockName.indexOf( 'rsmspark-cw-compat/' ) === 0 )
		);
	}

	function withCwEmbedWidthInspector( BlockEdit ) {
		return function ( props ) {
			if ( ! isContentWidgetsBlockName( props.name ) ) {
				return createElement( BlockEdit, props );
			}

			var panel = embedWidthInspectorPanel(
				props.attributes,
				props.setAttributes
			);

			if ( ! panel ) {
				return createElement( BlockEdit, props );
			}

			return createElement(
				Fragment,
				null,
				createElement( BlockEdit, props ),
				createElement(
					InspectorControls,
					{ group: 'settings' },
					panel
				)
			);
		};
	}

	if ( wp.hooks && wp.hooks.addFilter ) {
		wp.hooks.addFilter(
			'editor.BlockEdit',
			'rsmspark-cw/embed-width-inspector',
			withCwEmbedWidthInspector
		);
		wp.hooks.addFilter(
			'blockEditor.BlockEdit',
			'rsmspark-cw/embed-width-inspector',
			withCwEmbedWidthInspector
		);
	}

	window.rsmCwBlocksEditorReady = true;
} )( window.wp );
