/**
 * Per-element style controls for the Design panel.
 * Supports text | tabLabel | badge | button | buttonGroup | logoGroup | image | icon.
 */
import {
	BaseControl,
	Button,
	ColorIndicator,
	Dropdown,
	TextControl,
	ToggleControl,
	__experimentalDropdownContentWrapper as DropdownContentWrapper,
} from '@wordpress/components';
import { createElement, Fragment, useEffect, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { useThemeTokens } from '../hooks/useThemeTokens';
import {
	DEFAULT_BLOCK_FONT_FAMILY,
	DEFAULT_FONT_SIZES,
	FONT_WEIGHT_OPTIONS,
	IMAGE_WIDTH_OPTIONS,
	IMAGE_OBJECT_FIT_OPTIONS,
	CONTENT_ALIGN_OPTIONS,
	elementContentAlignVars,
	AUTHOR_IMAGE_ALIGN_OPTIONS,
	authorImageAlignVars,
} from '../styleFieldDefaults';
import { elementVarName, isMediaFullscreen, sanitizeElementClassName, sanitizeElementHtmlId } from '../cssVars';
import {
	BUTTON_ICON_POSITION_OPTIONS,
	buttonSupports,
	createDefaultHeroButton,
	duplicateHeroButtonAt,
	resolveButtonStyleSupports,
} from '../shared/buttonStyleBaseline';
import ButtonItemStyleEditor from './fields/ButtonItemStyleEditor';
import ButtonIconPicker from './fields/ButtonIconPicker';
import FlexibleUnitControl from './FlexibleUnitControl';
import FontSizeField from './FontSizeField';
import SearchableSelect from './SearchableSelect';
import ShadowFields from './ShadowFields';
import SpacingControl from './SpacingControl';
import IntelligentSpacing from './IntelligentSpacing';
import BoxRadiusField from './BoxRadiusField';
import BoxBorderField from './BoxBorderField';
import FillColorPalette, {
	DEFAULT_FILL_GRADIENT,
	DEFAULT_FILL_SOLID,
	isGradientValue,
} from './FillColorPalette';
import ConfirmModal from './ConfirmModal';
import {
	colorDropdownPopoverProps,
	useColorDropdownOutsideClose,
} from './colorDropdownFocus';

function ColorDropdown( {
	label,
	value,
	onChange,
	colorPalettes,
	enableGradient = false,
	help,
} ) {
	const [ open, setOpen ] = useState( false );
	const gradient = enableGradient && isGradientValue( value );
	const close = () => setOpen( false );
	const hostRef = useColorDropdownOutsideClose( open, close );

	return createElement(
		BaseControl,
		{
			label,
			help: help || undefined,
			className: 'bm-element-toolbar__field',
		},
		createElement(
			'div',
			{ ref: hostRef, className: 'bm-color-dropdown-host' },
			createElement( Dropdown, {
				open,
				onToggle: setOpen,
				onClose: close,
				className: 'bm-color-dropdown',
				contentClassName: 'bm-color-dropdown__content',
				popoverProps: colorDropdownPopoverProps( close ),
				renderToggle: ( { isOpen, onToggle } ) =>
					createElement(
						Button,
						{
							className: 'bm-color-dropdown__toggle',
							onClick: onToggle,
							'aria-expanded': isOpen,
							variant: 'secondary',
							__next40pxDefaultSize: true,
						},
						gradient
							? createElement( 'span', {
									className:
										'bm-color-dropdown__indicator bm-color-dropdown__indicator--gradient',
									style: { background: value },
									'aria-hidden': true,
							  } )
							: createElement( ColorIndicator, {
									className: 'bm-color-dropdown__indicator',
									colorValue: value || undefined,
							  } ),
						createElement(
							'span',
							null,
							value || __( 'Select color', 'joc-blockora-morph' )
						)
					),
				renderContent: () =>
					createElement(
						DropdownContentWrapper,
						{ paddingSize: 'medium' },
						createElement(
							'div',
							{ className: 'bm-color-dropdown__panel' },
							createElement( FillColorPalette, {
								palettes: colorPalettes,
								value: value || '',
								onChange: ( next ) =>
									onChange(
										next == null ||
											String( next ).trim() === ''
											? ''
											: next
									),
								enableGradient,
								solidDefault: DEFAULT_FILL_SOLID,
								gradientDefault: DEFAULT_FILL_GRADIENT,
								clearable: true,
								enableAlpha: true,
							} )
						)
					),
			} )
		)
	);
}

/**
 * Tabs Simple — tab title element panel.
 * Item chrome (Default / Active surface group) + tab-bar border + parent margin.
 */
function isTabNavResponsiveOn( value ) {
	const raw = String( value || '' )
		.trim()
		.toLowerCase();
	return raw === '1' || raw === 'on' || raw === 'true' || raw === 'yes';
}

const TAB_BURGER_SIZE_OPTIONS = [
	{ label: __( 'Small', 'joc-blockora-morph' ), value: 'small' },
	{ label: __( 'Medium', 'joc-blockora-morph' ), value: 'medium' },
	{ label: __( 'XL', 'joc-blockora-morph' ), value: 'xl' },
];

const TAB_BURGER_SIZE_PRESETS = {
	small: {
		'--bm-tab-burger-size': 'small',
		'--bm-tab-burger-width': '1rem',
		'--bm-tab-burger-bar': '1.5px',
		'--bm-tab-burger-gap': '5px',
		'--bm-tab-burger-toggle': '2rem',
	},
	medium: {
		'--bm-tab-burger-size': 'medium',
		'--bm-tab-burger-width': '1.25rem',
		'--bm-tab-burger-bar': '2px',
		'--bm-tab-burger-gap': '6px',
		'--bm-tab-burger-toggle': '2.5rem',
	},
	xl: {
		'--bm-tab-burger-size': 'xl',
		'--bm-tab-burger-width': '1.75rem',
		'--bm-tab-burger-bar': '3px',
		'--bm-tab-burger-gap': '8px',
		'--bm-tab-burger-toggle': '3.25rem',
	},
};

function resolveTabBurgerSize( value ) {
	const raw = String( value || '' )
		.trim()
		.toLowerCase();
	return Object.prototype.hasOwnProperty.call( TAB_BURGER_SIZE_PRESETS, raw )
		? raw
		: 'medium';
}

function tabBurgerSizePatch( value ) {
	return { ...TAB_BURGER_SIZE_PRESETS[ resolveTabBurgerSize( value ) ] };
}

const ACCORDION_ARROW_POSITION_OPTIONS = [
	{ label: __( 'Left', 'joc-blockora-morph' ), value: 'left' },
	{ label: __( 'Right', 'joc-blockora-morph' ), value: 'right' },
];

const ACCORDION_ARROW_SIZE_OPTIONS = [
	{ label: __( 'Small', 'joc-blockora-morph' ), value: 'small' },
	{ label: __( 'Medium', 'joc-blockora-morph' ), value: 'medium' },
	{ label: __( 'Large', 'joc-blockora-morph' ), value: 'large' },
	{ label: __( 'XLarge', 'joc-blockora-morph' ), value: 'xlarge' },
];

/**
 * Built-in + custom accordion indicator styles.
 * Add new built-ins here (and matching CSS under `.bm-accordion--arrow-style-{id}`).
 */
const ACCORDION_ARROW_STYLE_OPTIONS = [
	{ label: __( 'Arrow', 'joc-blockora-morph' ), value: 'arrow' },
	{
		label: __( 'Plus / Minus', 'joc-blockora-morph' ),
		value: 'plus-minus',
	},
	{
		label: __( 'Plus / Close (X)', 'joc-blockora-morph' ),
		value: 'plus-close',
	},
	{ label: __( 'Custom', 'joc-blockora-morph' ), value: 'custom' },
];

const ACCORDION_ARROW_STYLE_IDS = new Set(
	ACCORDION_ARROW_STYLE_OPTIONS.map( ( o ) => o.value )
);

function resolveAccordionArrowStyle( values = {} ) {
	const raw = String( values[ '--bm-accordion-arrow-style' ] || '' )
		.trim()
		.toLowerCase();
	if ( ACCORDION_ARROW_STYLE_IDS.has( raw ) ) {
		return raw;
	}
	// Legacy toggle: custom flag on → custom icons.
	if ( isTabNavResponsiveOn( values[ '--bm-accordion-arrow-custom' ] || '0' ) ) {
		return 'custom';
	}
	return 'arrow';
}

function accordionArrowStylePatch( value ) {
	const style = resolveAccordionArrowStyle( {
		'--bm-accordion-arrow-style': value,
	} );
	return {
		'--bm-accordion-arrow-style': style,
		// Keep legacy flag in sync for older CSS / interpolators.
		'--bm-accordion-arrow-custom': style === 'custom' ? '1' : '0',
	};
}

const ACCORDION_ARROW_SIZE_PRESETS = {
	small: {
		'--bm-accordion-arrow-size': 'small',
		'--bm-accordion-arrow-box-size': '1.5rem',
		'--bm-accordion-arrow-width': '0.35rem',
		'--bm-accordion-arrow-height': '0.35rem',
		'--bm-accordion-arrow-border': '1.5px',
		'--bm-accordion-plus-minus-stroke': '2px',
		'--bm-accordion-plus-minus-size': '8px',
		'--bm-accordion-arrow-font-size': '0.75rem',
		'--bm-accordion-arrow-open-offset': '30%',
	},
	medium: {
		'--bm-accordion-arrow-size': 'medium',
		'--bm-accordion-arrow-box-size': '2rem',
		'--bm-accordion-arrow-width': '0.45rem',
		'--bm-accordion-arrow-height': '0.45rem',
		'--bm-accordion-arrow-border': '2px',
		'--bm-accordion-plus-minus-stroke': '2px',
		'--bm-accordion-plus-minus-size': '10px',
		'--bm-accordion-arrow-font-size': '1rem',
		'--bm-accordion-arrow-open-offset': '45%',
	},
	large: {
		'--bm-accordion-arrow-size': 'large',
		'--bm-accordion-arrow-box-size': '2.5rem',
		'--bm-accordion-arrow-width': '0.55rem',
		'--bm-accordion-arrow-height': '0.55rem',
		'--bm-accordion-arrow-border': '2.5px',
		'--bm-accordion-plus-minus-stroke': '2px',
		'--bm-accordion-plus-minus-size': '14px',
		'--bm-accordion-arrow-font-size': '1.25rem',
		'--bm-accordion-arrow-open-offset': '30%',
	},
	xlarge: {
		'--bm-accordion-arrow-size': 'xlarge',
		'--bm-accordion-arrow-box-size': '3rem',
		'--bm-accordion-arrow-width': '0.7rem',
		'--bm-accordion-arrow-height': '0.7rem',
		'--bm-accordion-arrow-border': '3px',
		'--bm-accordion-plus-minus-stroke': '2px',
		'--bm-accordion-plus-minus-size': '16px',
		'--bm-accordion-arrow-font-size': '1.5rem',
		'--bm-accordion-arrow-open-offset': '30%',
	},
};

function resolveAccordionArrowSize( value ) {
	const raw = String( value || '' )
		.trim()
		.toLowerCase();
	return Object.prototype.hasOwnProperty.call(
		ACCORDION_ARROW_SIZE_PRESETS,
		raw
	)
		? raw
		: 'medium';
}

function accordionArrowSizePatch( value ) {
	return {
		...ACCORDION_ARROW_SIZE_PRESETS[ resolveAccordionArrowSize( value ) ],
	};
}

function resolveAccordionArrowPosition( value ) {
	return String( value || '' )
		.trim()
		.toLowerCase() === 'left'
		? 'left'
		: 'right';
}

function accordionArrowPositionPatch( value ) {
	const position = resolveAccordionArrowPosition( value );
	if ( position === 'left' ) {
		return {
			'--bm-accordion-arrow-position': 'left',
			'--bm-accordion-arrow-order': '-1',
			'--bm-accordion-arrow-ml': '0',
			'--bm-accordion-arrow-mr': '0',
		};
	}
	return {
		'--bm-accordion-arrow-position': 'right',
		'--bm-accordion-arrow-order': '0',
		'--bm-accordion-arrow-ml': 'auto',
		'--bm-accordion-arrow-mr': '0',
	};
}

function TabLabelStyleCards( { values = {}, onVarsPatch, onVarChange, colorPalettes } ) {
	const [ tabSurfaceState, setTabSurfaceState ] = useState( 'default' );
	const patch = ( next ) => {
		if ( typeof onVarsPatch === 'function' ) {
			onVarsPatch( next );
			return;
		}
		Object.entries( next || {} ).forEach( ( [ cssKey, value ] ) => {
			onVarChange?.( cssKey, value );
		} );
	};
	const read = ( cssKey ) => values[ cssKey ] || '';
	const isActive = tabSurfaceState === 'active';
	const bgKey = isActive ? '--bm-tab-active-bg' : '--bm-tab-bg';
	const colorKey = isActive ? '--bm-tab-active-color' : '--bm-tab-color';
	const borderKey = isActive ? '--bm-tab-active-border' : '--bm-tab-border';

	return createElement(
		Fragment,
		null,
		createElement(
			'div',
			{ className: 'bm-inspector-card' },
			createElement(
				'strong',
				{ className: 'bm-inspector-card__title' },
				__( 'Tab item', 'joc-blockora-morph' )
			),
			createElement(
				'div',
				{ className: 'bm-inspector-card bm-inspector-card--nested' },
				createElement(
					'strong',
					{ className: 'bm-inspector-card__title' },
					__( 'Surface', 'joc-blockora-morph' )
				),
				createElement(
					'div',
					{
						className: 'bm-element-toolbar__state-tabs',
						role: 'tablist',
						'aria-label': __(
							'Tab item state',
							'joc-blockora-morph'
						),
					},
					createElement(
						'button',
						{
							type: 'button',
							role: 'tab',
							className:
								'bm-element-toolbar__state-tab' +
								( ! isActive ? ' is-active' : '' ),
							'aria-selected': ! isActive,
							onClick: () => setTabSurfaceState( 'default' ),
						},
						__( 'Default', 'joc-blockora-morph' )
					),
					createElement(
						'button',
						{
							type: 'button',
							role: 'tab',
							className:
								'bm-element-toolbar__state-tab' +
								( isActive ? ' is-active' : '' ),
							'aria-selected': isActive,
							onClick: () => setTabSurfaceState( 'active' ),
						},
						__( 'Active', 'joc-blockora-morph' )
					)
				),
				isActive
					? createElement( ColorDropdown, {
							key: colorKey,
							label: __( 'Active color', 'joc-blockora-morph' ),
							value: read( colorKey ),
							onChange: ( next ) => {
								// Empty = fall back to Default / typography color.
								patch( {
									[ colorKey ]: next || '',
								} );
							},
							colorPalettes,
					  } )
					: null,
				createElement( ColorDropdown, {
					key: bgKey,
					label: isActive
						? __( 'Active background', 'joc-blockora-morph' )
						: __( 'Background', 'joc-blockora-morph' ),
					value: read( bgKey ),
					onChange: ( next ) => {
						// Active: empty = inherit Default. Default: clear → transparent.
						const value = isActive
							? next || ''
							: next || 'transparent';
						patch( { [ bgKey ]: value } );
					},
					colorPalettes,
				} ),
				createElement( BoxBorderField, {
					key: borderKey,
					field: isActive
						? {
								key: borderKey,
								label: __(
									'Active border',
									'joc-blockora-morph'
								),
								defaultWidth: '2px',
								defaultColor: '#3525cd',
								defaultStyle: 'solid',
								defaultSides: {
									top: '0',
									right: '0',
									bottom: '2px',
									left: '0',
								},
						  }
						: {
								key: borderKey,
								label: __( 'Border', 'joc-blockora-morph' ),
								defaultWidth: '0px',
								defaultColor: 'transparent',
								defaultStyle: 'solid',
								defaultSides: {
									top: '0',
									right: '0',
									bottom: '0',
									left: '0',
								},
						  },
					values,
					onChange: patch,
				} )
			),
			createElement( BoxRadiusField, {
				field: {
					key: '--bm-tab-border-radius',
					label: __( 'Corner radius', 'joc-blockora-morph' ),
				},
				values,
				onChange: patch,
			} ),
			createElement( IntelligentSpacing, {
				kind: __( 'Padding', 'joc-blockora-morph' ),
				modeKey: '--bm-tab-padding-link-mode',
				sides: [
					{
						key: '--bm-tab-padding-top',
						label: __( 'Top', 'joc-blockora-morph' ),
					},
					{
						key: '--bm-tab-padding-right',
						label: __( 'Right', 'joc-blockora-morph' ),
					},
					{
						key: '--bm-tab-padding-bottom',
						label: __( 'Bottom', 'joc-blockora-morph' ),
					},
					{
						key: '--bm-tab-padding-left',
						label: __( 'Left', 'joc-blockora-morph' ),
					},
				],
				values: {
					'--bm-tab-padding-top': read( '--bm-tab-padding-top' ),
					'--bm-tab-padding-right': read( '--bm-tab-padding-right' ),
					'--bm-tab-padding-bottom': read( '--bm-tab-padding-bottom' ),
					'--bm-tab-padding-left': read( '--bm-tab-padding-left' ),
					'--bm-tab-padding-link-mode': read(
						'--bm-tab-padding-link-mode'
					),
				},
				onChange: ( next ) => {
					const out = { ...next };
					if (
						Object.prototype.hasOwnProperty.call(
							next || {},
							'--bm-tab-padding-top'
						) ||
						Object.prototype.hasOwnProperty.call(
							next || {},
							'--bm-tab-padding-right'
						) ||
						Object.prototype.hasOwnProperty.call(
							next || {},
							'--bm-tab-padding-bottom'
						) ||
						Object.prototype.hasOwnProperty.call(
							next || {},
							'--bm-tab-padding-left'
						)
					) {
						const top =
							next[ '--bm-tab-padding-top' ] ??
							read( '--bm-tab-padding-top' );
						const right =
							next[ '--bm-tab-padding-right' ] ??
							read( '--bm-tab-padding-right' );
						const bottom =
							next[ '--bm-tab-padding-bottom' ] ??
							read( '--bm-tab-padding-bottom' );
						const left =
							next[ '--bm-tab-padding-left' ] ??
							read( '--bm-tab-padding-left' );
						out[ '--bm-tab-padding-y' ] = top || bottom || '';
						out[ '--bm-tab-padding-x' ] = left || right || '';
					}
					patch( out );
				},
			} )
		),
		createElement(
			'div',
			{ className: 'bm-inspector-card' },
			createElement(
				'strong',
				{ className: 'bm-inspector-card__title' },
				__( 'Tab bar', 'joc-blockora-morph' )
			),
			createElement( BoxBorderField, {
				field: {
					key: '--bm-tab-list-border',
					label: __( 'Tab border', 'joc-blockora-morph' ),
					defaultWidth: '0px',
					defaultColor: 'rgba(0, 0, 0, 0.12)',
					defaultStyle: 'solid',
					defaultSides: {
						top: '0',
						right: '0',
						bottom: '1px',
						left: '0',
					},
				},
				values,
				onChange: patch,
			} ),
			createElement( ToggleControl, {
				label: __( 'Responsive menu', 'joc-blockora-morph' ),
				help: __(
					'On tablet and below, collapse tab titles into a burger menu on the right.',
					'joc-blockora-morph'
				),
				checked: isTabNavResponsiveOn(
					read( '--bm-tab-nav-responsive' )
				),
				onChange: ( next ) => {
					patch( {
						'--bm-tab-nav-responsive': next ? '1' : '0',
					} );
				},
				__nextHasNoMarginBottom: true,
			} ),
			isTabNavResponsiveOn( read( '--bm-tab-nav-responsive' ) )
				? createElement(
						Fragment,
						null,
						createElement( SearchableSelect, {
							label: __( 'Burger menu size', 'joc-blockora-morph' ),
							className: 'bm-element-toolbar__field',
							value: resolveTabBurgerSize(
								read( '--bm-tab-burger-size' )
							),
							options: TAB_BURGER_SIZE_OPTIONS,
							onChange: ( next ) =>
								patch( tabBurgerSizePatch( next ) ),
							searchableThreshold: 8,
						} ),
						createElement( ColorDropdown, {
							label: __(
								'Burger menu color',
								'joc-blockora-morph'
							),
							value:
								read( '--bm-tab-burger-color' ) || '#4b5563',
							onChange: ( next ) => {
								patch( {
									'--bm-tab-burger-color':
										next || '#4b5563',
								} );
							},
							colorPalettes,
						} )
				  )
				: null
		)
	);
}

function writeSpacingPatch( set, setMany, patch ) {
	if ( typeof setMany === 'function' ) {
		setMany( patch );
		return;
	}
	Object.entries( patch || {} ).forEach( ( [ prop, value ] ) => {
		set( prop, value || '' );
	} );
}

function MarginYFields( { get, set, setMany, showPaddingX = true } ) {
	const marginModeKey = 'margin-link-mode';
	const paddingModeKey = 'padding-link-mode';
	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Spacing', 'joc-blockora-morph' )
		),
		createElement( IntelligentSpacing, {
			kind: __( 'Margin', 'joc-blockora-morph' ),
			modeKey: marginModeKey,
			sides: [
				{
					key: 'margin-top',
					label: __( 'Margin top', 'joc-blockora-morph' ),
				},
				{
					key: 'margin-bottom',
					label: __( 'Margin bottom', 'joc-blockora-morph' ),
				},
			],
			values: {
				'margin-top': get( 'margin-top' ) || '',
				'margin-bottom': get( 'margin-bottom' ) || '',
				[ marginModeKey ]: get( marginModeKey ) || '',
			},
			onChange: ( next ) => writeSpacingPatch( set, setMany, next ),
		} ),
		showPaddingX
			? createElement( IntelligentSpacing, {
					kind: __( 'Padding', 'joc-blockora-morph' ),
					modeKey: paddingModeKey,
					sides: [
						{
							key: 'padding-left',
							label: __( 'Padding left', 'joc-blockora-morph' ),
						},
						{
							key: 'padding-right',
							label: __( 'Padding right', 'joc-blockora-morph' ),
						},
					],
					values: {
						'padding-left': get( 'padding-left' ) || '',
						'padding-right': get( 'padding-right' ) || '',
						[ paddingModeKey ]: get( paddingModeKey ) || '',
					},
					onChange: ( next ) =>
						writeSpacingPatch( set, setMany, next ),
			  } )
			: null
	);
}

/** All four margin sides (no padding) — icon Styles spacing. */
function MarginAllFields( { get, set, setMany } ) {
	const modeKey = 'margin-link-mode';
	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Spacing', 'joc-blockora-morph' )
		),
		createElement( IntelligentSpacing, {
			kind: __( 'Margin', 'joc-blockora-morph' ),
			modeKey,
			sides: [
				{
					key: 'margin-top',
					label: __( 'Top', 'joc-blockora-morph' ),
				},
				{
					key: 'margin-right',
					label: __( 'Right', 'joc-blockora-morph' ),
				},
				{
					key: 'margin-bottom',
					label: __( 'Bottom', 'joc-blockora-morph' ),
				},
				{
					key: 'margin-left',
					label: __( 'Left', 'joc-blockora-morph' ),
				},
			],
			values: {
				'margin-top': get( 'margin-top' ) || '',
				'margin-right': get( 'margin-right' ) || '',
				'margin-bottom': get( 'margin-bottom' ) || '',
				'margin-left': get( 'margin-left' ) || '',
				[ modeKey ]: get( modeKey ) || '',
			},
			onChange: ( next ) => writeSpacingPatch( set, setMany, next ),
		} )
	);
}

/** Margin bottom only — e.g. testimonial profile name. */
function MarginBottomOnlyFields( { get, set } ) {
	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Spacing', 'joc-blockora-morph' )
		),
		createElement( SpacingControl, {
			label: __( 'Margin bottom', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'margin-bottom' ) || '',
			onChange: ( next ) => set( 'margin-bottom', next || '' ),
		} )
	);
}

/** Padding on all four sides (no margin) — used by tab content panels. */
function PaddingAllFields( { get, set, setMany } ) {
	const modeKey = 'padding-link-mode';
	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Spacing', 'joc-blockora-morph' )
		),
		createElement( IntelligentSpacing, {
			kind: __( 'Padding', 'joc-blockora-morph' ),
			modeKey,
			sides: [
				{
					key: 'padding-top',
					label: __( 'Top', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-right',
					label: __( 'Right', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-bottom',
					label: __( 'Bottom', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-left',
					label: __( 'Left', 'joc-blockora-morph' ),
				},
			],
			values: {
				'padding-top': get( 'padding-top' ) || '',
				'padding-right': get( 'padding-right' ) || '',
				'padding-bottom': get( 'padding-bottom' ) || '',
				'padding-left': get( 'padding-left' ) || '',
				[ modeKey ]: get( modeKey ) || '',
			},
			onChange: ( next ) => writeSpacingPatch( set, setMany, next ),
		} )
	);
}

/**
 * Icon Content surface: padding, border, radius, and box-shadow.
 * Defaults stay empty so the icon has no frame until set.
 */
function IconSurfaceFields( {
	get,
	set,
	setMany,
	values = {},
	onVarsPatch,
	onVarChange,
	colorPalettes,
} ) {
	const patchVars = ( patch ) => {
		if ( typeof onVarsPatch === 'function' ) {
			onVarsPatch( patch );
			return;
		}
		Object.entries( patch || {} ).forEach( ( [ k, v ] ) =>
			onVarChange?.( k, v )
		);
	};
	const paddingModeKey = 'padding-link-mode';

	return createElement(
		Fragment,
		null,
		createElement( ColorDropdown, {
			label: __( 'Icon background', 'joc-blockora-morph' ),
			value: get( 'bg' ),
			onChange: ( next ) => set( 'bg', next || '' ),
			colorPalettes,
		} ),
		createElement( IntelligentSpacing, {
			kind: __( 'Icon padding', 'joc-blockora-morph' ),
			modeKey: paddingModeKey,
			sides: [
				{
					key: 'padding-top',
					label: __( 'Top', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-right',
					label: __( 'Right', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-bottom',
					label: __( 'Bottom', 'joc-blockora-morph' ),
				},
				{
					key: 'padding-left',
					label: __( 'Left', 'joc-blockora-morph' ),
				},
			],
			values: {
				'padding-top': get( 'padding-top' ) || '',
				'padding-right': get( 'padding-right' ) || '',
				'padding-bottom': get( 'padding-bottom' ) || '',
				'padding-left': get( 'padding-left' ) || '',
				[ paddingModeKey ]: get( paddingModeKey ) || '',
			},
			onChange: ( next ) => writeSpacingPatch( set, setMany, next ),
		} ),
		createElement( BoxBorderField, {
			field: {
				key: elementVarName( 'icon', 'border' ),
				label: __( 'Border', 'joc-blockora-morph' ),
				defaultWidth: '0px',
				defaultColor: 'transparent',
				defaultStyle: 'none',
				defaultSides: {
					top: '0',
					right: '0',
					bottom: '0',
					left: '0',
				},
			},
			values,
			onChange: patchVars,
		} ),
		createElement( BoxRadiusField, {
			field: {
				key: elementVarName( 'icon', 'border-radius' ),
				label: __( 'Border radius', 'joc-blockora-morph' ),
			},
			values,
			onChange: patchVars,
		} ),
		createElement( ShadowFields, {
			label: __( 'Box shadow', 'joc-blockora-morph' ),
			preset: get( 'shadow-preset' ),
			color: get( 'shadow-color' ),
			css: get( 'shadow' ) || 'none',
			colorPalettes,
			onChange: ( next ) => {
				setMany( {
					'shadow-preset': next.preset,
					'shadow-color': next.color,
					shadow: next.css,
				} );
			},
		} )
	);
}

function ImageStyleCard( {
	get,
	set,
	elementKey = 'image',
	values = {},
	onVarsPatch,
	showFullscreen = false,
	fullscreenValue = 'framed',
	onFullscreenChange,
	showWidth = true,
} ) {
	const width = get( 'width' ) || '100%';
	const widthPreset = IMAGE_WIDTH_OPTIONS.some( ( o ) => o.value === width )
		? width
		: 'custom';
	const objectFitRaw = String( get( 'object-fit' ) || 'cover' ).trim();
	const objectFit = IMAGE_OBJECT_FIT_OPTIONS.some(
		( o ) => o.value === objectFitRaw
	)
		? objectFitRaw
		: 'cover';
	const fullscreenOn = isMediaFullscreen( fullscreenValue );
	const radiusField = {
		key: elementVarName( elementKey, 'border-radius' ),
		label: __( 'Border radius', 'joc-blockora-morph' ),
	};

	const widthOptions = [
		...IMAGE_WIDTH_OPTIONS,
		{
			label: __( 'Custom', 'joc-blockora-morph' ),
			value: 'custom',
		},
	];
	const framed = ! showFullscreen || ! fullscreenOn;

	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Image', 'joc-blockora-morph' )
		),
		showFullscreen
			? createElement( ToggleControl, {
					label: __( 'Fullscreen media', 'joc-blockora-morph' ),
					help: fullscreenOn
						? __(
								'On for this viewport: media fills its column edge-to-edge (and the full block width when columns stack). Set separately for Desktop, Tablet, and Mobile. Columns gap and padding still apply.',
								'joc-blockora-morph'
						  )
						: __(
								'Off for this viewport: media stays framed so you can set width and radius. Switch Desktop / Tablet / Mobile above to set fullscreen per breakpoint.',
								'joc-blockora-morph'
						  ),
					className:
						'bm-element-toolbar__field bm-style-field--media-frame',
					checked: fullscreenOn,
					onChange: ( next ) => {
						if ( typeof onFullscreenChange !== 'function' ) {
							return;
						}
						// Prefer explicit boolean from ToggleControl; fall back to flip.
						const on =
							typeof next === 'boolean' ? next : ! fullscreenOn;
						onFullscreenChange( on ? 'fullscreen' : 'framed' );
					},
					__nextHasNoMarginBottom: true,
			  } )
			: null,
		framed && showWidth
			? createElement(
					'p',
					{ className: 'bm-element-toolbar__hint' },
					__(
						'Width is capped at 100% so the image stays fluid on smaller screens.',
						'joc-blockora-morph'
					)
			  )
			: null,
		framed && showWidth
			? createElement( SearchableSelect, {
					label: __( 'Width', 'joc-blockora-morph' ),
					className: 'bm-element-toolbar__field',
					value: widthPreset,
					options: widthOptions,
					onChange: ( next ) => {
						if ( ! next ) {
							return;
						}
						if ( next === 'custom' ) {
							if (
								IMAGE_WIDTH_OPTIONS.some(
									( o ) => o.value === width
								)
							) {
								set( 'width', '480px' );
							}
							return;
						}
						set( 'width', next );
					},
					searchableThreshold: 99,
			  } )
			: null,
		framed && showWidth && widthPreset === 'custom'
			? createElement( FlexibleUnitControl, {
					label: __( 'Custom width', 'joc-blockora-morph' ),
					className: 'bm-element-toolbar__field',
					value: width,
					onChange: ( next ) => set( 'width', next || '100%' ),
					units: [
						{ value: '%', label: '%', default: 100 },
						{ value: 'px', label: 'px', default: 480 },
						{ value: 'rem', label: 'rem', default: 30 },
					],
			  } )
			: null,
		framed
			? createElement( FlexibleUnitControl, {
					label: __( 'Height', 'joc-blockora-morph' ),
					help: showFullscreen
						? __(
								'Used when columns stack. Width stays max 100%. Object fit controls how the image fills the box.',
								'joc-blockora-morph'
						  )
						: __(
								'Image height. Width is always 100%. Object fit controls how the image fills the box.',
								'joc-blockora-morph'
						  ),
					className: 'bm-element-toolbar__field',
					value: get( 'height' ) || '',
					onChange: ( next ) => set( 'height', next || '' ),
					units: [
						{ value: 'px', label: 'px', default: 240 },
						{ value: 'rem', label: 'rem', default: 15 },
						{ value: 'vh', label: 'vh', default: 40 },
						{ value: '%', label: '%', default: 100 },
					],
			  } )
			: null,
		createElement( SearchableSelect, {
			label: __( 'Object fit', 'joc-blockora-morph' ),
			help: __(
				'How the image fills its frame. Cover crops to fill; Contain shows the full image.',
				'joc-blockora-morph'
			),
			className: 'bm-element-toolbar__field',
			value: objectFit,
			options: IMAGE_OBJECT_FIT_OPTIONS,
			onChange: ( next ) => set( 'object-fit', next || 'cover' ),
			searchableThreshold: 99,
		} ),
		framed
			? createElement( BoxRadiusField, {
					field: radiusField,
					values,
					onChange: ( patch ) => {
						if ( typeof onVarsPatch === 'function' ) {
							onVarsPatch( patch );
							return;
						}
						const prefix = `--bm-${ elementKey }-`;
						Object.entries( patch || {} ).forEach(
							( [ cssKey, value ] ) => {
								if (
									String( cssKey ).startsWith( prefix )
								) {
									set(
										String( cssKey ).slice(
											prefix.length
										),
										value
									);
								}
							}
						);
					},
			  } )
			: null
	);
}

function TypographyCard( {
	get,
	set,
	sizes,
	fontFamilyOptions,
	colorPalettes,
	showColor = true,
} ) {
	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'Typography', 'joc-blockora-morph' )
		),
		showColor
			? createElement( ColorDropdown, {
					label: __( 'Color', 'joc-blockora-morph' ),
					value: get( 'color' ),
					onChange: ( next ) => set( 'color', next ),
					colorPalettes,
			  } )
			: null,
		createElement( SearchableSelect, {
			label: __( 'Font family', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'font-family' ) || DEFAULT_BLOCK_FONT_FAMILY,
			options: fontFamilyOptions,
			onChange: ( next ) =>
				set( 'font-family', next || DEFAULT_BLOCK_FONT_FAMILY ),
			searchableThreshold: 1,
			previewFontFamily: true,
		} ),
		createElement( FontSizeField, {
			label: __( 'Size', 'joc-blockora-morph' ),
			value: get( 'font-size' ),
			fontSizes: sizes,
			onChange: ( next ) => set( 'font-size', next ),
		} ),
		createElement( SearchableSelect, {
			label: __( 'Weight', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'font-weight' ) || '400',
			options: FONT_WEIGHT_OPTIONS,
			onChange: ( next ) => set( 'font-weight', next || '400' ),
		} ),
		createElement( TextControl, {
			label: __( 'Line height', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'line-height' ) || '',
			onChange: ( next ) => set( 'line-height', next || '' ),
			placeholder: '1.5',
		} ),
		createElement( TextControl, {
			label: __( 'Letter spacing', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'letter-spacing' ) || '',
			onChange: ( next ) => set( 'letter-spacing', next || '' ),
			placeholder: 'normal',
		} )
	);
}

/**
 * Per-section alignment for testimonials (quote / message / author).
 * Writes --bm-{prefix}-align* vars used by markup (falls back to Card Surface).
 */
function ElementSectionAlignCard( {
	label,
	prefix,
	values = {},
	onVarsPatch,
	onVarChange,
} ) {
	const alignKey = `--bm-${ prefix }-align`;
	const raw = String( values[ alignKey ] || '' ).trim();
	const current =
		raw === 'center' || raw === 'left' || raw === 'right' ? raw : 'left';

	const applyPatch = ( patch ) => {
		if ( typeof onVarsPatch === 'function' ) {
			onVarsPatch( patch );
			return;
		}
		Object.entries( patch || {} ).forEach( ( [ k, v ] ) =>
			onVarChange?.( k, v )
		);
	};

	const imageRaw = String( values[ '--bm-author-image-align' ] || '' ).trim();
	const imageAlign =
		imageRaw === 'top' ||
		imageRaw === 'bottom' ||
		imageRaw === 'right' ||
		imageRaw === 'left'
			? imageRaw
			: 'left';

	return createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			label || __( 'Alignment', 'joc-blockora-morph' )
		),
		createElement( SearchableSelect, {
			label: __( 'Alignment', 'joc-blockora-morph' ),
			help: __(
				'Overrides Card Surface content alignment for this part.',
				'joc-blockora-morph'
			),
			className: 'bm-element-toolbar__field',
			value: current,
			options: CONTENT_ALIGN_OPTIONS,
			onChange: ( next ) => {
				const align =
					next === 'center' || next === 'left' || next === 'right'
						? next
						: 'left';
				applyPatch( elementContentAlignVars( prefix, align ) );
			},
		} ),
		prefix === 'author'
			? createElement( SearchableSelect, {
					label: __( 'Image alignment', 'joc-blockora-morph' ),
					help: __(
						'Place the avatar beside or above/below the name — does not move the author group.',
						'joc-blockora-morph'
					),
					className: 'bm-element-toolbar__field',
					value: imageAlign,
					options: AUTHOR_IMAGE_ALIGN_OPTIONS,
					onChange: ( next ) => {
						const align =
							next === 'top' ||
							next === 'bottom' ||
							next === 'right' ||
							next === 'left'
								? next
								: 'left';
						applyPatch( authorImageAlignVars( align ) );
					},
			  } )
			: null
	);
}

/**
 * @param {object}   props
 * @param {object}   props.field
 * @param {object}   props.values
 * @param {Function} props.onVarChange
 * @param {Function} [props.onVarsPatch] Apply multiple CSS vars in one update (required in Gutenberg).
 * @param {string}   props.breakpoint
 * @param {Function} [props.onBack]
 * @param {Array}    [props.buttonLibrary]
 * @param {Array}    [props.buttons]          Repeater button items
 * @param {Function} [props.onButtonsChange]  (nextButtons) => void
 * @param {number|null} [props.focusButtonIndex] Scroll/highlight this button style card
 * @param {import('react').ReactNode} [props.contentEditor] Fields-tab content control (reuse)
 * @param {object}   [props.contentAttrs] Content attrs (label, url, icon, …) for standalone button
 * @param {Function} [props.onContentAttrsChange] ( partial ) => void
 * @param {boolean}  [props.sharedIcon] When true, icon content applies to every collection item
 * @param {import('react').ReactNode} [props.stylesGate] Customize-instance gate overlay
 * @param {boolean} [props.stylesLocked] Blur/lock style controls behind the gate
 * @param {boolean} [props.elementVisible] False when Content visibility is Hidden for this element
 * @param {number}  [props.minButtons] Minimum buttons to keep (default 1 — hide element instead)
 * @param {boolean} [props.allowManageButtons] Add/remove buttons (My Blocks). False in Gutenberg/FE — count comes from the block.
 * @param {Function} [props.onRemoveCard] Delete focused collection item (tab/card)
 * @param {boolean} [props.canRemoveCard]
 * @param {string} [props.removeCardLabel] Confirm label context (tab title)
 */
export default function ElementToolbar( {
	field,
	values = {},
	onVarChange,
	onVarsPatch,
	breakpoint = 'desktop',
	onBack,
	buttonLibrary = [],
	buttons = null,
	onButtonsChange,
	focusButtonIndex = null,
	contentEditor = null,
	contentAttrs = null,
	onContentAttrsChange,
	sharedIcon = false,
	stylesGate = null,
	stylesLocked = false,
	elementVisible = true,
	slideIndex = null,
	minButtons = 1,
	allowManageButtons = true,
	showMediaFullscreen = false,
	mediaFrameValue = 'framed',
	onMediaFrameChange,
	onRemoveCard = null,
	canRemoveCard = false,
	removeCardLabel = '',
} ) {
	const { colorPalettes, fontSizes, fontFamilyOptions } = useThemeTokens();
	const sizes = fontSizes?.length ? fontSizes : DEFAULT_FONT_SIZES;
	const key = field?.key;
	const rawType = field?.elementType || 'text';
	const elementType =
		rawType === 'button' ||
		rawType === 'buttonGroup' ||
		rawType === 'logoGroup' ||
		rawType === 'badge' ||
		rawType === 'image' ||
		rawType === 'icon' ||
		rawType === 'tabLabel' ||
		rawType === 'accordionArrow'
			? rawType
			: key === 'tabTitle'
				? 'tabLabel'
				: key === 'accordionArrow'
					? 'accordionArrow'
					: 'text';
	const [ confirmRemoveCard, setConfirmRemoveCard ] = useState( false );
	useEffect( () => {
		if (
			elementType !== 'buttonGroup' ||
			focusButtonIndex === null ||
			focusButtonIndex === undefined
		) {
			return undefined;
		}
		const selector = `[data-bm-btn-style="${ focusButtonIndex }"]`;
		let el = null;
		let highlightTimer = 0;
		const frame = window.requestAnimationFrame( () => {
			el = document.querySelector( selector );
			if ( ! el ) {
				return;
			}
			el.scrollIntoView( { behavior: 'smooth', block: 'nearest' } );
			el.classList.add( 'is-focused' );
			highlightTimer = window.setTimeout( () => {
				el?.classList.remove( 'is-focused' );
			}, 1800 );
		} );
		return () => {
			window.cancelAnimationFrame( frame );
			window.clearTimeout( highlightTimer );
			el?.classList.remove( 'is-focused' );
		};
		// Only when the focused index changes — not on every button style edit.
	}, [ elementType, focusButtonIndex ] );

	if ( ! key || ! field?.elementStyle ) {
		return null;
	}

	const get = ( prop ) => values[ elementVarName( key, prop ) ] || '';
	const set = ( prop, value ) => onVarChange( elementVarName( key, prop ), value );
	/** Desktop-only HTML attrs (class / id) — not breakpoint overrides. */
	const setHtmlAttr = ( prop, value ) => {
		const clean =
			prop === 'id'
				? sanitizeElementHtmlId( value )
				: sanitizeElementClassName( value );
		if ( typeof onVarChange === 'function' ) {
			onVarChange( elementVarName( key, prop ), clean, 'desktop' );
		}
	};
	/** Batch related props so Gutenberg setAttributes does not drop sibling keys. */
	const setMany = ( props ) => {
		const patch = {};
		Object.entries( props || {} ).forEach( ( [ prop, value ] ) => {
			patch[ elementVarName( key, prop ) ] = value;
		} );
		if ( typeof onVarsPatch === 'function' ) {
			onVarsPatch( patch );
			return;
		}
		Object.entries( props || {} ).forEach( ( [ prop, value ] ) => {
			set( prop, value );
		} );
	};

	const htmlAttrsCard = createElement(
		'div',
		{ className: 'bm-inspector-card' },
		createElement(
			'strong',
			{ className: 'bm-inspector-card__title' },
			__( 'HTML attributes', 'joc-blockora-morph' )
		),
		createElement( TextControl, {
			label: __( 'Class name', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'class' ),
			onChange: ( next ) => setHtmlAttr( 'class', next || '' ),
			help: __(
				'Extra CSS classes for this element (space-separated).',
				'joc-blockora-morph'
			),
			__next40pxDefaultSize: true,
			__nextHasNoMarginBottom: true,
		} ),
		createElement( TextControl, {
			label: __( 'ID', 'joc-blockora-morph' ),
			className: 'bm-element-toolbar__field',
			value: get( 'id' ),
			onChange: ( next ) => setHtmlAttr( 'id', next || '' ),
			help: __(
				'Unique HTML id for this element. Avoid duplicates on the same page.',
				'joc-blockora-morph'
			),
			__next40pxDefaultSize: true,
			__nextHasNoMarginBottom: true,
		} )
	);

	const isTestimonialEl = String( field?.selector || '' ).includes(
		'bm-testimonial'
	);
	const testimonialAlignCard =
		isTestimonialEl && key === 'icon'
			? createElement( ElementSectionAlignCard, {
					label: __( 'Quote alignment', 'joc-blockora-morph' ),
					prefix: 'quote',
					values,
					onVarsPatch,
					onVarChange,
			  } )
			: isTestimonialEl && key === 'content'
				? createElement( ElementSectionAlignCard, {
						label: __( 'Text alignment', 'joc-blockora-morph' ),
						prefix: 'message',
						values,
						onVarsPatch,
						onVarChange,
				  } )
				: isTestimonialEl &&
					  ( key === 'name' ||
							key === 'company' ||
							key === 'image' )
					? createElement( ElementSectionAlignCard, {
							label: __(
								'Author alignment',
								'joc-blockora-morph'
							),
							prefix: 'author',
							values,
							onVarsPatch,
							onVarChange,
					  } )
					: null;

	const baseLabel = field.label || key;
	const elementLabel =
		slideIndex != null && Number.isFinite( Number( slideIndex ) )
			? sprintf(
					/* translators: 1: slide number (1-based), 2: element name */
					__( 'Slide #%1$d — %2$s', 'joc-blockora-morph' ),
					Number( slideIndex ) + 1,
					baseLabel
			  )
			: baseLabel;

	const header =
		onBack
			? createElement(
					'div',
					{ className: 'bm-element-toolbar__header' },
					createElement(
						Button,
						{
							variant: 'secondary',
							onClick: onBack,
							className: 'bm-element-toolbar__back',
							icon: 'arrow-left-alt2',
						},
						__( 'Back', 'joc-blockora-morph' )
					),
					createElement(
						'div',
						{
							className: 'bm-element-toolbar__context',
							role: 'status',
						},
						createElement(
							'p',
							{ className: 'bm-element-toolbar__context-eyebrow' },
							__( 'Currently editing', 'joc-blockora-morph' )
						),
						createElement(
							'strong',
							{ className: 'bm-element-toolbar__context-title' },
							elementLabel
						),
						createElement(
							'p',
							{ className: 'bm-element-toolbar__context-note' },
							sprintf(
								/* translators: %s: element name, e.g. Media */
								__(
									'Customization below applies only to “%s”, not the whole block.',
									'joc-blockora-morph'
								),
								elementLabel
							)
						)
					)
			  )
			: null;

	const showCardDelete =
		typeof onRemoveCard === 'function' && canRemoveCard;

	const removeItemConfirm =
		key === 'accordionTitle'
			? __( 'Delete item', 'joc-blockora-morph' )
			: elementType === 'tabLabel' || key === 'tabTitle'
			? __( 'Delete tab', 'joc-blockora-morph' )
			: __( 'Delete card', 'joc-blockora-morph' );
	const removeItemTitle =
		key === 'accordionTitle'
			? __( 'Delete this item?', 'joc-blockora-morph' )
			: elementType === 'tabLabel' || key === 'tabTitle'
			? __( 'Delete this tab?', 'joc-blockora-morph' )
			: __( 'Delete this card?', 'joc-blockora-morph' );
	const removeItemFallback =
		key === 'accordionTitle'
			? __( 'This item', 'joc-blockora-morph' )
			: elementType === 'tabLabel' || key === 'tabTitle'
			? __( 'This tab', 'joc-blockora-morph' )
			: __( 'This card', 'joc-blockora-morph' );

	const canEditItemContent = typeof onContentAttrsChange === 'function';
	const itemContentAttrs =
		contentAttrs && typeof contentAttrs === 'object' ? contentAttrs : {};
	const titleIconsEnabled = isTabNavResponsiveOn(
		values[ '--bm-title-icons' ] || '0'
	);
	const titleIconControls =
		( elementType === 'tabLabel' || key === 'accordionTitle' ) &&
		( canEditItemContent || typeof onVarsPatch === 'function' || typeof onVarChange === 'function' )
			? createElement(
					Fragment,
					null,
					createElement( ToggleControl, {
						label: __( 'Show icons', 'joc-blockora-morph' ),
						help:
							elementType === 'tabLabel'
								? __(
										'Show or hide the left icon on every tab title.',
										'joc-blockora-morph'
								  )
								: __(
										'Show or hide the left icon on every accordion title.',
										'joc-blockora-morph'
								  ),
						checked: titleIconsEnabled,
						onChange: ( next ) => {
							const on = Boolean( next );
							const patch = {
								'--bm-title-icons': on ? '1' : '0',
								'--bm-title-icons-display': on
									? 'inline-flex'
									: 'none',
							};
							if ( typeof onVarsPatch === 'function' ) {
								onVarsPatch( patch );
								return;
							}
							Object.entries( patch ).forEach( ( [ k, v ] ) =>
								onVarChange?.( k, v )
							);
						},
						__nextHasNoMarginBottom: true,
					} ),
					canEditItemContent
						? createElement(
								Fragment,
								null,
								createElement(
									'p',
									{ className: 'bm-element-toolbar__hint' },
									elementType === 'tabLabel'
										? __(
												'Icon and size apply only to the selected tab. Show icons must be on for them to appear.',
												'joc-blockora-morph'
										  )
										: __(
												'Icon and size apply only to the selected accordion item. Show icons must be on for them to appear.',
												'joc-blockora-morph'
										  )
								),
								createElement( ButtonIconPicker, {
									value: String( itemContentAttrs.icon || '' ),
									onChange: ( next ) => {
										const icon = next || '';
										if (
											icon &&
											! titleIconsEnabled &&
											( typeof onVarsPatch ===
												'function' ||
												typeof onVarChange ===
													'function' )
										) {
											const patch = {
												'--bm-title-icons': '1',
												'--bm-title-icons-display':
													'inline-flex',
											};
											if (
												typeof onVarsPatch ===
												'function'
											) {
												onVarsPatch( patch );
											} else {
												Object.entries( patch ).forEach(
													( [ k, v ] ) =>
														onVarChange?.( k, v )
												);
											}
										}
										onContentAttrsChange( {
											icon,
											iconSize:
												String(
													itemContentAttrs.iconSize ||
														''
												).trim() || '1.25rem',
										} );
									},
								} ),
								createElement( FlexibleUnitControl, {
									label: __(
										'Icon size',
										'joc-blockora-morph'
									),
									value:
										String(
											itemContentAttrs.iconSize || ''
										).trim() || '1.25rem',
									onChange: ( next ) =>
										onContentAttrsChange( {
											iconSize: next || '1.25rem',
										} ),
								} )
						  )
						: null
			  )
			: null;

	const canEditIconContent = typeof onContentAttrsChange === 'function';
	const iconContentAttrs =
		contentAttrs && typeof contentAttrs === 'object' ? contentAttrs : {};
	const iconContentControls =
		elementType === 'icon'
			? createElement(
					Fragment,
					null,
					! contentEditor
						? createElement(
								Fragment,
								null,
								createElement(
									'p',
									{ className: 'bm-element-toolbar__hint' },
									sharedIcon
										? __(
												'Quote icon is shared across every testimonial. Changing it updates all cards.',
												'joc-blockora-morph'
										  )
										: __(
												'Pick an icon from any enabled library, or remove it to hide the icon.',
												'joc-blockora-morph'
										  )
								),
								canEditIconContent
									? createElement( ButtonIconPicker, {
											value: String(
												iconContentAttrs.icon || ''
											),
											onChange: ( next ) =>
												onContentAttrsChange( {
													icon: next || '',
												} ),
									  } )
									: null
						  )
						: null,
					createElement( FlexibleUnitControl, {
						label: __( 'Icon size', 'joc-blockora-morph' ),
						value: get( 'font-size' ) || '2rem',
						onChange: ( next ) =>
							set( 'font-size', next || '2rem' ),
					} ),
					createElement( ColorDropdown, {
						label: __( 'Icon color', 'joc-blockora-morph' ),
						value: get( 'color' ),
						onChange: ( next ) => set( 'color', next ),
						colorPalettes,
					} ),
					canEditIconContent
						? createElement( ColorDropdown, {
								label: __(
									'This icon color',
									'joc-blockora-morph'
								),
								help: __(
									'Overrides Icon color for this card only. Leave empty to use Icon color.',
									'joc-blockora-morph'
								),
								value: String(
									iconContentAttrs.iconColor || ''
								),
								onChange: ( next ) =>
									onContentAttrsChange( {
										iconColor: next || '',
									} ),
								colorPalettes,
						  } )
						: null,
					createElement( IconSurfaceFields, {
						get,
						set,
						setMany,
						values,
						onVarsPatch,
						onVarChange,
						colorPalettes,
					} )
			  )
			: null;

	const contentBlock =
		contentEditor ||
		showCardDelete ||
		titleIconControls ||
		iconContentControls
			? createElement(
					Fragment,
					null,
					createElement(
						'div',
						{
							className:
								'bm-inspector-card bm-element-toolbar__content',
						},
						createElement(
							'div',
							{ className: 'bm-inspector-card__title-row' },
							createElement(
								'strong',
								{ className: 'bm-inspector-card__title' },
								__( 'Content', 'joc-blockora-morph' )
							),
							showCardDelete
								? createElement( Button, {
										className:
											'bm-inspector-card__title-action',
										variant: 'tertiary',
										isDestructive: true,
										icon: 'trash',
										label: removeItemConfirm,
										showTooltip: true,
										onClick: () =>
											setConfirmRemoveCard( true ),
								  } )
								: null
						),
						contentEditor,
						titleIconControls,
						iconContentControls
					),
					createElement( ConfirmModal, {
						open: confirmRemoveCard,
						title: removeItemTitle,
						description: sprintf(
							/* translators: %s: item or tab title */
							__(
								'“%s” and its content will be permanently removed from this block.',
								'joc-blockora-morph'
							),
							removeCardLabel || removeItemFallback
						),
						icon: 'trash',
						tone: 'danger',
						confirmLabel: removeItemConfirm,
						onCancel: () => setConfirmRemoveCard( false ),
						onConfirm: () => {
							setConfirmRemoveCard( false );
							onRemoveCard?.();
						},
					} )
			  )
			: null;

	const renderPanelWith = ( ungatedNodes, styleNodes = [] ) =>
		createElement(
			'div',
			{
				className:
					'bm-element-toolbar bm-element-toolbar--panel' +
					( elementVisible ? '' : ' is-element-hidden' ),
			},
			header,
			elementVisible
				? null
				: createElement(
						'p',
						{
							className:
								'bm-element-toolbar__hint bm-element-toolbar__hint--hidden',
						},
						sprintf(
							/* translators: %s: Desktop, Tablet, or Mobile */
							__(
								'This element is Hidden for %s. Style controls stay available but won’t appear on that viewport until you show it again.',
								'joc-blockora-morph'
							),
							breakpoint === 'tablet'
								? __( 'Tablet', 'joc-blockora-morph' )
								: breakpoint === 'mobile'
								? __( 'Mobile', 'joc-blockora-morph' )
								: __( 'Desktop', 'joc-blockora-morph' )
						)
				  ),
			contentBlock,
			ungatedNodes,
			createElement(
				'div',
				{
					className:
						'bm-element-toolbar__styles' +
						( stylesLocked ? ' is-gated' : '' ) +
						( elementVisible ? '' : ' is-element-hidden' ),
				},
				stylesGate,
				...styleNodes,
				htmlAttrsCard
			)
		);

	const renderPanel = ( ...styleNodes ) =>
		renderPanelWith( null, styleNodes );

	if ( elementType === 'image' ) {
		return renderPanel(
			testimonialAlignCard,
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				__(
					'Click the image in the preview anytime to reselect it.',
					'joc-blockora-morph'
				)
			),
			createElement( ImageStyleCard, {
				get,
				set,
				elementKey: key,
				values,
				onVarsPatch,
				showFullscreen: showMediaFullscreen,
				fullscreenValue: mediaFrameValue,
				onFullscreenChange: onMediaFrameChange,
				showWidth: field?.hideWidth !== true,
			} ),
			createElement( MarginYFields, { get, set, setMany } )
		);
	}

	if ( elementType === 'icon' ) {
		return renderPanel(
			testimonialAlignCard,
			createElement( MarginAllFields, { get, set, setMany } )
		);
	}

	if ( elementType === 'accordionArrow' ) {
		const arrowVisible = isTabNavResponsiveOn(
			values[ '--bm-accordion-arrow' ] || '1'
		);
		const arrowStyle = resolveAccordionArrowStyle( values );
		const arrowIsCustom = arrowStyle === 'custom';
		const canEditArrowIcons = typeof onContentAttrsChange === 'function';
		const arrowAttrs =
			contentAttrs && typeof contentAttrs === 'object' ? contentAttrs : {};
		const patchArrowVars = ( patch, bp = 'desktop' ) => {
			if ( typeof onVarsPatch === 'function' ) {
				onVarsPatch( patch, bp );
				return;
			}
			Object.entries( patch || {} ).forEach( ( [ k, v ] ) =>
				onVarChange?.( k, v, bp )
			);
		};
		return renderPanel(
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				__(
					'Arrow styles apply to every accordion item. Built-in indicators animate on open; Custom uses separate closed and open icons.',
					'joc-blockora-morph'
				)
			),
			createElement(
				'div',
				{ className: 'bm-inspector-card' },
				createElement(
					'strong',
					{ className: 'bm-inspector-card__title' },
					__( 'Arrow', 'joc-blockora-morph' )
				),
				createElement( ToggleControl, {
					label: __( 'Show arrow icon', 'joc-blockora-morph' ),
					help: __(
						'Show or hide the expand/collapse indicator on accordion titles.',
						'joc-blockora-morph'
					),
					checked: arrowVisible,
					onChange: ( next ) => {
						const on = Boolean( next );
						const patch = {
							'--bm-accordion-arrow': on ? '1' : '0',
							'--bm-accordion-arrow-display': on
								? 'inline-flex'
								: 'none',
						};
						if ( on ) {
							Object.assign(
								patch,
								accordionArrowPositionPatch(
									values[ '--bm-accordion-arrow-position' ] ||
										'right'
								),
								accordionArrowSizePatch(
									values[ '--bm-accordion-arrow-size' ] ||
										'medium'
								),
								accordionArrowStylePatch( arrowStyle )
							);
						}
						patchArrowVars( patch );
					},
					__nextHasNoMarginBottom: true,
				} ),
				arrowVisible
					? createElement(
							Fragment,
							null,
							createElement( SearchableSelect, {
								label: __(
									'Arrow position',
									'joc-blockora-morph'
								),
								className: 'bm-element-toolbar__field',
								value: resolveAccordionArrowPosition(
									values[ '--bm-accordion-arrow-position' ]
								),
								options: ACCORDION_ARROW_POSITION_OPTIONS,
								onChange: ( next ) => {
									patchArrowVars(
										accordionArrowPositionPatch( next )
									);
								},
								searchableThreshold: 8,
							} ),
							createElement( SearchableSelect, {
								label: __( 'Arrow size', 'joc-blockora-morph' ),
								className: 'bm-element-toolbar__field',
								value: resolveAccordionArrowSize(
									values[ '--bm-accordion-arrow-size' ]
								),
								options: ACCORDION_ARROW_SIZE_OPTIONS,
								onChange: ( next ) => {
									patchArrowVars(
										accordionArrowSizePatch( next )
									);
								},
								searchableThreshold: 8,
							} ),
							createElement( SearchableSelect, {
								label: __( 'Icon style', 'joc-blockora-morph' ),
								help: __(
									'Choose an animated built-in indicator, or Custom for your own closed and open icons.',
									'joc-blockora-morph'
								),
								className: 'bm-element-toolbar__field',
								value: arrowStyle,
								options: ACCORDION_ARROW_STYLE_OPTIONS,
								onChange: ( next ) => {
									patchArrowVars(
										accordionArrowStylePatch( next )
									);
								},
								searchableThreshold: 8,
							} ),
							arrowIsCustom && canEditArrowIcons
								? createElement(
										Fragment,
										null,
										createElement(
											'div',
											{
												className:
													'bm-element-toolbar__field',
											},
											createElement(
												'strong',
												{
													className:
														'bm-inspector-card__subtitle',
												},
												__(
													'Closed icon',
													'joc-blockora-morph'
												)
											),
											createElement( ButtonIconPicker, {
												label: __(
													'Closed icon',
													'joc-blockora-morph'
												),
												value: String(
													arrowAttrs.arrowIconClosed ||
														''
												),
												onChange: ( next ) => {
													onContentAttrsChange( {
														arrowIconClosed: String(
															next || ''
														),
													} );
												},
											} )
										),
										createElement(
											'div',
											{
												className:
													'bm-element-toolbar__field',
											},
											createElement(
												'strong',
												{
													className:
														'bm-inspector-card__subtitle',
												},
												__(
													'Open icon',
													'joc-blockora-morph'
												)
											),
											createElement( ButtonIconPicker, {
												label: __(
													'Open icon',
													'joc-blockora-morph'
												),
												value: String(
													arrowAttrs.arrowIconOpen ||
														''
												),
												onChange: ( next ) => {
													onContentAttrsChange( {
														arrowIconOpen: String(
															next || ''
														),
													} );
												},
											} )
										)
								  )
								: null
					  )
					: null
			),
			arrowVisible
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Arrow surface', 'joc-blockora-morph' )
						),
						createElement( ColorDropdown, {
							label: __( 'Arrow color', 'joc-blockora-morph' ),
							value: get( 'color' ),
							onChange: ( next ) => set( 'color', next ),
							colorPalettes,
						} ),
						createElement( ColorDropdown, {
							label: __( 'Background', 'joc-blockora-morph' ),
							value: get( 'bg' ),
							onChange: ( next ) => set( 'bg', next ),
							colorPalettes,
							enableGradient: true,
						} ),
						createElement( BoxBorderField, {
							field: {
								key: elementVarName( key, 'border' ),
								label: __( 'Border', 'joc-blockora-morph' ),
								defaultWidth: '0px',
								defaultStyle: 'none',
								defaultColor: 'transparent',
								defaultSides: {
									top: '0',
									right: '0',
									bottom: '0',
									left: '0',
								},
							},
							values,
							onChange: ( patch ) => {
								if ( typeof onVarsPatch === 'function' ) {
									onVarsPatch( patch );
									return;
								}
								Object.entries( patch || {} ).forEach(
									( [ cssKey, value ] ) =>
										onVarChange?.( cssKey, value )
								);
							},
						} ),
						createElement( BoxRadiusField, {
							field: {
								key: elementVarName( key, 'border-radius' ),
								label: __(
									'Border radius',
									'joc-blockora-morph'
								),
							},
							values,
							onChange: ( patch ) => {
								if ( typeof onVarsPatch === 'function' ) {
									onVarsPatch( patch );
									return;
								}
								Object.entries( patch || {} ).forEach(
									( [ cssKey, value ] ) =>
										onVarChange?.( cssKey, value )
								);
							},
						} ),
						createElement( ShadowFields, {
							label: __( 'Box shadow', 'joc-blockora-morph' ),
							preset: get( 'shadow-preset' ),
							color: get( 'shadow-color' ),
							css: get( 'shadow' ) || 'none',
							colorPalettes,
							onChange: ( next ) => {
								setMany( {
									'shadow-preset': next.preset,
									'shadow-color': next.color,
									shadow: next.css,
								} );
							},
						} )
				  )
				: null
		);
	}

	if ( elementType === 'logoGroup' ) {
		return renderPanel(
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				__(
					'Layout controls apply to the logo row. Add or replace logos in Content above or the Fields tab.',
					'joc-blockora-morph'
				)
			),
			createElement(
				'div',
				{ className: 'bm-inspector-card' },
				createElement(
					'strong',
					{ className: 'bm-inspector-card__title' },
					__( 'Logo layout', 'joc-blockora-morph' )
				),
				createElement( SpacingControl, {
					label: __( 'Logo height', 'joc-blockora-morph' ),
					className: 'bm-element-toolbar__field',
					value: get( 'height' ) || '',
					onChange: ( next ) => set( 'height', next || '' ),
				} ),
				createElement( SpacingControl, {
					label: __( 'Gap between logos', 'joc-blockora-morph' ),
					className: 'bm-element-toolbar__field',
					value: get( 'gap' ) || '',
					onChange: ( next ) => set( 'gap', next || '' ),
				} ),
			),
			createElement( MarginYFields, { get, set, setMany } )
		);
	}

	if ( elementType === 'buttonGroup' ) {
		const buttonItems = Array.isArray( buttons ) ? buttons : [];
		const updateButton = ( index, nextItem ) => {
			if ( typeof onButtonsChange !== 'function' ) {
				return;
			}
			onButtonsChange(
				buttonItems.map( ( it, i ) => ( i === index ? nextItem : it ) )
			);
		};
		const addButton = () => {
			if ( ! allowManageButtons || typeof onButtonsChange !== 'function' ) {
				return;
			}
			onButtonsChange( [ ...buttonItems, createDefaultHeroButton() ] );
		};
		const removeButton = ( index ) => {
			if ( ! allowManageButtons || typeof onButtonsChange !== 'function' ) {
				return;
			}
			if ( buttonItems.length <= minButtons ) {
				return;
			}
			onButtonsChange( buttonItems.filter( ( _, i ) => i !== index ) );
		};
		const duplicateButton = ( index ) => {
			if ( ! allowManageButtons || typeof onButtonsChange !== 'function' ) {
				return;
			}
			const result = duplicateHeroButtonAt( buttonItems, index );
			if ( result ) {
				onButtonsChange( result.next );
			}
		};
		const canRemoveButton =
			allowManageButtons && buttonItems.length > minButtons;

		return renderPanel(
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				allowManageButtons
					? __(
							'Style each button here. New buttons use a default label; edit label, URL, and icon in Content / Fields. Style reference is saved with the block.',
							'joc-blockora-morph'
					  )
					: __(
							'Button count comes from My Blocks. Edit label, URL, and icon in Fields. Style reference and Design follow the rules for this placement.',
							'joc-blockora-morph'
					  )
			),
			createElement(
				'div',
				{ className: 'bm-inspector-card' },
				createElement(
					'strong',
					{ className: 'bm-inspector-card__title' },
					__( 'Buttons', 'joc-blockora-morph' )
				),
				buttonItems.length
					? null
					: createElement(
							'p',
							{ className: 'bm-element-toolbar__hint' },
							allowManageButtons
								? __(
										'No buttons yet. Add one to set default CTAs for this block.',
										'joc-blockora-morph'
								  )
								: __(
										'No buttons on this block. Add them in My Blocks first.',
										'joc-blockora-morph'
								  )
					  ),
				...buttonItems.map( ( item, index ) =>
					createElement( ButtonItemStyleEditor, {
						key: `btn-style-${ index }`,
						item,
						buttonIndex: index,
						focus: focusButtonIndex === index,
						title:
							item?.link?.title
								? `${ __( 'Button', 'joc-blockora-morph' ) } ${
										index + 1
								  }: ${ item.link.title }`
								: `${ __( 'Button', 'joc-blockora-morph' ) } ${
										index + 1
								  }`,
						onChange: ( next ) => updateButton( index, next ),
						onDuplicate: allowManageButtons
							? () => duplicateButton( index )
							: undefined,
						onRemove: canRemoveButton
							? () => removeButton( index )
							: undefined,
						showContent: false,
						styleSupports: resolveButtonStyleSupports( field ),
						buttonLibrary,
						fontFamilyOptions,
						colorPalettes,
					} )
				),
				allowManageButtons
					? createElement(
							Button,
							{
								variant: 'secondary',
								className: 'bm-repeater__add',
								icon: 'plus-alt2',
								onClick: addButton,
								disabled: typeof onButtonsChange !== 'function',
							},
							__( 'Add button', 'joc-blockora-morph' )
					  )
					: null
			),
			createElement(
				'div',
				{
					key: 'btn-row',
					className: 'bm-inspector-card',
				},
				createElement(
					'strong',
					{ className: 'bm-inspector-card__title' },
					__( 'Button row', 'joc-blockora-morph' )
				),
				createElement( SpacingControl, {
					label: __( 'Gap between buttons', 'joc-blockora-morph' ),
					className: 'bm-element-toolbar__field',
					value: get( 'gap' ) || '',
					onChange: ( next ) => set( 'gap', next || '' ),
				} )
			),
			createElement( MarginYFields, {
				key: 'btn-margin',
				get,
				set,
				setMany,
			} )
		);
	}

	if ( elementType === 'button' ) {
		const supports = resolveButtonStyleSupports( field );
		const canEditContent = typeof onContentAttrsChange === 'function';
		const attrs = contentAttrs && typeof contentAttrs === 'object' ? contentAttrs : {};
		const patchAttrs = ( partial ) => {
			if ( canEditContent ) {
				onContentAttrsChange( partial );
			}
		};
		const applySize = ( next ) => {
			const size = next || 'medium';
			if ( size === 'custom' ) {
				set( 'size', 'custom' );
				return;
			}
			const pads = {
				small: { y: '0.4rem', x: '0.9rem' },
				medium: { y: '0.75rem', x: '1.25rem' },
				large: { y: '1rem', x: '1.75rem' },
			};
			const pad = pads[ size ] || pads.medium;
			setMany( {
				size,
				'pad-y': pad.y,
				'pad-x': pad.x,
			} );
		};
		const iconPosOptions = BUTTON_ICON_POSITION_OPTIONS.map( ( opt ) => ( {
			...opt,
			label:
				opt.value === 'right'
					? __( 'Right', 'joc-blockora-morph' )
					: __( 'Left', 'joc-blockora-morph' ),
		} ) );

		return renderPanel(
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				__(
					'These styles are reused when this button is selected as a style reference in heroes. Edit label and URL in Fields.',
					'joc-blockora-morph'
				)
			),
			buttonSupports( supports, 'colors' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Color', 'joc-blockora-morph' )
						),
						createElement(
							'div',
							{ className: 'bm-element-toolbar__color-grid' },
							createElement( ColorDropdown, {
								label: __( 'Background', 'joc-blockora-morph' ),
								value: get( 'bg' ),
								onChange: ( next ) => set( 'bg', next ),
								colorPalettes,
								enableGradient: true,
							} ),
							createElement( ColorDropdown, {
								label: __( 'Text', 'joc-blockora-morph' ),
								value: get( 'color' ),
								onChange: ( next ) => set( 'color', next ),
								colorPalettes,
							} ),
							createElement( ColorDropdown, {
								label: __( 'Background (hover)', 'joc-blockora-morph' ),
								value: get( 'bg-hover' ),
								onChange: ( next ) => set( 'bg-hover', next ),
								colorPalettes,
								enableGradient: true,
							} ),
							createElement( ColorDropdown, {
								label: __( 'Text (hover)', 'joc-blockora-morph' ),
								value: get( 'color-hover' ),
								onChange: ( next ) => set( 'color-hover', next ),
								colorPalettes,
							} )
						)
				  )
				: null,
			buttonSupports( supports, 'fontFamily' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Typography', 'joc-blockora-morph' )
						),
						createElement( SearchableSelect, {
							label: __( 'Font family', 'joc-blockora-morph' ),
							className: 'bm-element-toolbar__field',
							value: get( 'font-family' ) || DEFAULT_BLOCK_FONT_FAMILY,
							options: fontFamilyOptions,
							onChange: ( next ) =>
								set(
									'font-family',
									next || DEFAULT_BLOCK_FONT_FAMILY
								),
							searchableThreshold: 1,
							previewFontFamily: true,
						} ),
						createElement( FontSizeField, {
							label: __( 'Size', 'joc-blockora-morph' ),
							className: 'bm-element-toolbar__field',
							fontSizes: sizes,
							value: get( 'font-size' ) || '',
							onChange: ( next ) => set( 'font-size', next || '' ),
						} )
				  )
				: null,
			buttonSupports( supports, 'size' ) || buttonSupports( supports, 'padding' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Dimensions', 'joc-blockora-morph' )
						),
						buttonSupports( supports, 'size' )
							? createElement( SearchableSelect, {
									label: __( 'Button size', 'joc-blockora-morph' ),
									className: 'bm-element-toolbar__field',
									value: get( 'size' ) || 'medium',
									options: [
										{
											label: __( 'Small', 'joc-blockora-morph' ),
											value: 'small',
										},
										{
											label: __( 'Medium', 'joc-blockora-morph' ),
											value: 'medium',
										},
										{
											label: __( 'Large', 'joc-blockora-morph' ),
											value: 'large',
										},
										{
											label: __( 'Custom', 'joc-blockora-morph' ),
											value: 'custom',
										},
									],
									onChange: applySize,
									searchableThreshold: 99,
							  } )
							: null,
						buttonSupports( supports, 'padding' )
							? createElement(
									'div',
									{ className: 'bm-btn-style-panel__row' },
									createElement( FlexibleUnitControl, {
										label: __( 'Padding Y', 'joc-blockora-morph' ),
										value: get( 'pad-y' ) || '',
										onChange: ( next ) =>
											setMany( {
												size: 'custom',
												'pad-y': next || '',
											} ),
									} ),
									createElement( FlexibleUnitControl, {
										label: __( 'Padding X', 'joc-blockora-morph' ),
										value: get( 'pad-x' ) || '',
										onChange: ( next ) =>
											setMany( {
												size: 'custom',
												'pad-x': next || '',
											} ),
									} )
							  )
							: null
				  )
				: null,
			buttonSupports( supports, 'borderRadius' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Border', 'joc-blockora-morph' )
						),
						createElement( BoxBorderField, {
							field: {
								key: elementVarName( key, 'border' ),
								label: __( 'Border', 'joc-blockora-morph' ),
								defaultWidth: '0px',
								defaultStyle: 'none',
								defaultColor: 'transparent',
								defaultSides: {
									top: '0',
									right: '0',
									bottom: '0',
									left: '0',
								},
							},
							values,
							onChange: ( patch ) => {
								if ( typeof onVarsPatch === 'function' ) {
									onVarsPatch( patch );
									return;
								}
								Object.entries( patch || {} ).forEach(
									( [ cssKey, value ] ) =>
										onVarChange?.( cssKey, value )
								);
							},
						} ),
						createElement( BoxRadiusField, {
							field: {
								key: elementVarName( key, 'border-radius' ),
								label: __( 'Radius', 'joc-blockora-morph' ),
							},
							values,
							onChange: ( patch ) => {
								if ( typeof onVarsPatch === 'function' ) {
									onVarsPatch( patch );
									return;
								}
								Object.entries( patch || {} ).forEach(
									( [ cssKey, value ] ) =>
										onVarChange?.( cssKey, value )
								);
							},
						} )
				  )
				: null,
			buttonSupports( supports, 'boxShadow' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Shadow', 'joc-blockora-morph' )
						),
						createElement( ShadowFields, {
							label: __( 'Box shadow', 'joc-blockora-morph' ),
							preset: get( 'shadow-preset' ),
							color: get( 'shadow-color' ),
							css: get( 'shadow' ) || 'none',
							colorPalettes,
							onChange: ( next ) => {
								setMany( {
									'shadow-preset': next.preset,
									'shadow-color': next.color,
									shadow: next.css,
								} );
							},
						} )
				  )
				: null,
			buttonSupports( supports, 'icon' )
				? createElement(
						'div',
						{ className: 'bm-inspector-card' },
						createElement(
							'strong',
							{ className: 'bm-inspector-card__title' },
							__( 'Icon', 'joc-blockora-morph' )
						),
						createElement(
							'p',
							{ className: 'bm-element-toolbar__hint' },
							__(
								'Pick an icon from any enabled library in Global Settings → Icons.',
								'joc-blockora-morph'
							)
						),
						createElement( ButtonIconPicker, {
							value: String( attrs.icon || '' ),
							onChange: ( next ) =>
								patchAttrs( { icon: next || '' } ),
						} ),
						createElement( SearchableSelect, {
							label: __( 'Position', 'joc-blockora-morph' ),
							className: 'bm-element-toolbar__field',
							value:
								String( attrs.iconPosition || 'right' ).toLowerCase() ===
								'left'
									? 'left'
									: 'right',
							options: iconPosOptions,
							onChange: ( next ) =>
								patchAttrs( {
									iconPosition:
										next === 'left' ? 'left' : 'right',
								} ),
							searchableThreshold: 99,
						} ),
						createElement( FlexibleUnitControl, {
							label: __( 'Icon size', 'joc-blockora-morph' ),
							value: get( 'icon-size' ) || '20px',
							onChange: ( next ) =>
								set( 'icon-size', next || '20px' ),
						} ),
						createElement( ColorDropdown, {
							label: __( 'Icon color', 'joc-blockora-morph' ),
							value: get( 'icon-color' ),
							onChange: ( next ) => set( 'icon-color', next ),
							colorPalettes,
						} )
				  )
				: null
		);
	}

	const badgeSurface =
		elementType === 'badge'
			? createElement(
					'div',
					{ className: 'bm-inspector-card' },
					createElement(
						'strong',
						{ className: 'bm-inspector-card__title' },
						field?.label
							? sprintf(
									/* translators: %s: element label e.g. Tag */
									__( '%s surface', 'joc-blockora-morph' ),
									field.label
							  )
							: __( 'Badge surface', 'joc-blockora-morph' )
					),
					createElement( ColorDropdown, {
						label: __( 'Background', 'joc-blockora-morph' ),
						value: get( 'bg' ),
						onChange: ( next ) => set( 'bg', next ),
						colorPalettes,
						enableGradient: true,
					} ),
					createElement( BoxBorderField, {
						field: {
							key: elementVarName( key, 'border' ),
							label: __( 'Border', 'joc-blockora-morph' ),
							defaultWidth: '0px',
							defaultStyle: 'none',
							defaultColor: 'transparent',
							defaultSides: {
								top: '0',
								right: '0',
								bottom: '0',
								left: '0',
							},
						},
						values,
						onChange: ( patch ) => {
							if ( typeof onVarsPatch === 'function' ) {
								onVarsPatch( patch );
								return;
							}
							Object.entries( patch || {} ).forEach(
								( [ cssKey, value ] ) =>
									onVarChange?.( cssKey, value )
							);
						},
					} ),
					createElement( BoxRadiusField, {
						field: {
							key: elementVarName( key, 'border-radius' ),
							label: __( 'Border radius', 'joc-blockora-morph' ),
						},
						values,
						onChange: ( patch ) => {
							if ( typeof onVarsPatch === 'function' ) {
								onVarsPatch( patch );
								return;
							}
							Object.entries( patch || {} ).forEach(
								( [ cssKey, value ] ) =>
									onVarChange?.( cssKey, value )
							);
						},
					} ),
					createElement( ShadowFields, {
						label: __( 'Box shadow', 'joc-blockora-morph' ),
						preset: get( 'shadow-preset' ),
						color: get( 'shadow-color' ),
						css: get( 'shadow' ) || 'none',
						colorPalettes,
						onChange: ( next ) => {
							setMany( {
								'shadow-preset': next.preset,
								'shadow-color': next.color,
								shadow: next.css,
							} );
						},
					} )
			  )
			: null;

	const accordionSurface =
		key === 'accordionTitle' || key === 'accordionContent'
			? createElement(
					'div',
					{ className: 'bm-inspector-card' },
					createElement(
						'strong',
						{ className: 'bm-inspector-card__title' },
						key === 'accordionTitle'
							? __( 'Title surface', 'joc-blockora-morph' )
							: __( 'Content surface', 'joc-blockora-morph' )
					),
					createElement( ColorDropdown, {
						label: __( 'Background', 'joc-blockora-morph' ),
						value: get( 'bg' ),
						onChange: ( next ) => set( 'bg', next ),
						colorPalettes,
						enableGradient: true,
					} ),
					key === 'accordionTitle'
						? createElement( BoxBorderField, {
								field: {
									key: '--bm-accordionTitle-border',
									label: __(
										'Border bottom',
										'joc-blockora-morph'
									),
									fixedSides: [ 'bottom' ],
									defaultWidth: '1px',
									defaultColor: 'rgba(0, 0, 0, 0.1)',
									defaultStyle: 'solid',
									defaultSides: {
										top: '0',
										right: '0',
										bottom: '0',
										left: '0',
									},
								},
								values,
								onChange: ( patch ) => {
									if ( typeof onVarsPatch === 'function' ) {
										onVarsPatch( patch );
										return;
									}
									Object.entries( patch || {} ).forEach(
										( [ k, v ] ) => onVarChange?.( k, v )
									);
								},
						  } )
						: null,
					key === 'accordionTitle'
						? createElement(
								Fragment,
								null,
								createElement( SearchableSelect, {
								label: __(
									'Initially open',
									'joc-blockora-morph'
								),
								help: __(
									'Which accordion items start expanded on load.',
									'joc-blockora-morph'
								),
								value: ( () => {
									const raw = String(
										values[
											'--bm-accordion-open-default'
										] ?? ''
									)
										.trim()
										.toLowerCase();
									if (
										raw === 'all' ||
										raw === '1' ||
										raw === 'true' ||
										raw === 'yes' ||
										raw === 'on'
									) {
										return 'all';
									}
									if (
										raw === 'none' ||
										raw === '0' ||
										raw === 'false' ||
										raw === 'off' ||
										raw === 'closed'
									) {
										return 'none';
									}
									return 'first';
								} )(),
								options: [
									{
										label: __(
											'Open first item',
											'joc-blockora-morph'
										),
										value: 'first',
									},
									{
										label: __(
											'Open all',
											'joc-blockora-morph'
										),
										value: 'all',
									},
									{
										label: __(
											'Close all',
											'joc-blockora-morph'
										),
										value: 'none',
									},
								],
								onChange: ( next ) => {
									const mode =
										next === 'all'
											? 'all'
											: next === 'none'
												? 'none'
												: 'first';
									const patch = {
										'--bm-accordion-open-default': mode,
									};
									// "Open all" implies several may stay open.
									if ( mode === 'all' ) {
										patch[ '--bm-accordion-multiple' ] =
											'1';
									}
									if ( typeof onVarsPatch === 'function' ) {
										onVarsPatch( patch, 'desktop' );
										return;
									}
									Object.entries( patch ).forEach(
										( [ k, v ] ) =>
											onVarChange?.( k, v, 'desktop' )
									);
								},
								searchableThreshold: 99,
						  } ),
								( () => {
									const raw = String(
										values[
											'--bm-accordion-open-default'
										] ?? ''
									)
										.trim()
										.toLowerCase();
									const isOpenAll =
										raw === 'all' ||
										raw === '1' ||
										raw === 'true' ||
										raw === 'yes' ||
										raw === 'on';
									if ( isOpenAll ) {
										return null;
									}
									return createElement( ToggleControl, {
										label: __(
											'Allow multiple items open',
											'joc-blockora-morph'
										),
										help: __(
											'When off, opening one item closes the others.',
											'joc-blockora-morph'
										),
										checked: isTabNavResponsiveOn(
											values[
												'--bm-accordion-multiple'
											] ?? '1'
										),
										onChange: ( next ) => {
											const on = Boolean( next );
											const patch = {
												'--bm-accordion-multiple': on
													? '1'
													: '0',
											};
											if (
												typeof onVarsPatch ===
												'function'
											) {
												onVarsPatch( patch, 'desktop' );
												return;
											}
											onVarChange?.(
												'--bm-accordion-multiple',
												on ? '1' : '0',
												'desktop'
											);
										},
										__nextHasNoMarginBottom: true,
									} );
								} )()
						  )
						: null
			  )
			: null;

	if ( elementType === 'tabLabel' ) {
		return renderPanel(
			createElement(
				'p',
				{ className: 'bm-element-toolbar__hint' },
				__(
					'Typography applies to every tab title. Surface and tab-bar styles are shared across all tabs.',
					'joc-blockora-morph'
				)
			),
			createElement( TypographyCard, {
				get,
				set,
				sizes,
				fontFamilyOptions,
				colorPalettes,
				showColor: true,
			} ),
			createElement( TabLabelStyleCards, {
				values,
				onVarsPatch,
				onVarChange,
				colorPalettes,
			} )
		);
	}

	const spacingCard =
		key === 'company'
			? null
			: key === 'name'
				? createElement( MarginBottomOnlyFields, { get, set } )
				: key === 'tabContent' ||
				  key === 'accordionContent' ||
				  key === 'accordionTitle'
					? createElement( PaddingAllFields, { get, set, setMany } )
					: createElement( MarginYFields, { get, set, setMany } );

	return renderPanel(
		createElement(
			'p',
			{ className: 'bm-element-toolbar__hint' },
			__(
				'These styles apply only to this element. Heading tags in Fields do not reset typography here.',
				'joc-blockora-morph'
			)
		),
		testimonialAlignCard,
		accordionSurface,
		createElement( TypographyCard, {
			get,
			set,
			sizes,
			fontFamilyOptions,
			colorPalettes,
			showColor: true,
		} ),
		badgeSurface,
		spacingCard
	);
}
