import { useCallback, useEffect, useMemo, useRef, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Button, SelectControl } from '@wordpress/components';
import {
	effective,
	merge,
	normalize,
	setBreakpointVar,
	setSharedDesktopVar,
	styleableFieldsInOrder,
} from '../../cssVars';
import { isStyleFieldVisibleAtBreakpoint } from '../../styleFieldDefaults';
import { api, ownerApi } from '../api';
import { renderPreview } from '../components/PreviewModal';
import IconPicker from '../components/IconPicker';
import BlockIdentityIcon from '../components/BlockIdentityIcon';
import PreviewFrame from '../components/PreviewFrame';
import ViewportEditBanner from '../components/ViewportEditBanner';
import ElementToolbar from '../../components/ElementToolbar';
import StyleFieldList from '../../components/StyleFieldList';
import SchemaFieldControl from '../../components/fields/SchemaFieldControl';
import SlidesList, {
	remapFocusSlideIndex,
} from '../../components/SlidesList';
import {
	collectionItemChromeActionsAllowed,
	collectionItemsLabel,
	collectionMinItems,
	collectionRemoveConfirmCopy,
	collectionSharesIcon,
	duplicateCollectionItemAt,
	findCollectionRepeater,
	isPricingCollectionBlock,
	moveCollectionItemAt,
	partialTouchesSharedIcon,
	pickSharedIconPartial,
} from '../../shared/collectionRepeater';
import { isElementVisible } from '../../interpolate';
import { prefetchInlineIconsFromValue } from '../../shared/iconRegistry';
import {
	focusBmSliderSlide,
	slideLayoutLabel,
	slideOrderDefaults,
	SLIDE_LAYOUT_OPTIONS,
} from '../../shared/sliderElements';
import {
	createDefaultHeroSlide,
	MIN_HERO_SLIDES,
} from '../../shared/sliderHelpers';
import { syncLocalizedLibraryCssVars } from '../../shared/libraryCssVars';
import { endPreviewContentEditForLinkEditor } from '../../shared/previewContentEdit';
import SlideElementsList from '../../shared/SlideElementsList';
import { useDocumentHistory } from '../../shared/useDocumentHistory';
import { cloneState } from '../../shared/attributeHistory';
import {
	elementsOrderDefaultsFromSchema,
	serializeElementsOrder,
	setElementsOrderAtBreakpoint,
} from '../../shared/elementsOrder';
import {
	layoutMediaFields,
	ContentFieldEditor,
	contentFieldVisibility,
	FieldControls,
} from '../../shared/BlockInspectorPanel';
import {
	effectiveSlideLayoutValues,
	ensureSlideLayoutCssVars,
	isColumnsSlideLayout,
	isSlideLevelStyleGroup,
	slideStyleGroupsFor,
	columnMediaVisibilityKeyBreakpoints,
	mediaAllowsFullscreenFrame,
} from '../../shared/slideLayoutStyle';
import { isMediaFullscreen } from '../../cssVars';
import useUnsavedChanges from '../hooks/useUnsavedChanges';
import useSaveShortcut from '../hooks/useSaveShortcut';
import FloatingToast from '../components/ui/FloatingToast';
import TemplateUpdateModal from '../components/TemplateUpdateModal';
import {
	useButtonLibrary,
	refreshButtonLibrary,
} from '../../shared/buttonLibrary';
import { brandLogoUrl } from '../../shared/brandLogo';
import { scrollInspectorToTop } from '../../shared/inspectorScroll';
import BrandLoader from '../components/BrandLoader';

const POSITIONS = [
	{ id: 'right', label: __( 'Panel right', 'joc-blockora-morph' ), icon: 'align-right' },
	{ id: 'left', label: __( 'Panel left', 'joc-blockora-morph' ), icon: 'align-left' },
	{ id: 'bottom', label: __( 'Panel bottom', 'joc-blockora-morph' ), icon: 'align-full-width' },
];

function tabClass( active ) {
	return active
		? 'bm-editor-tab is-active'
		: 'bm-editor-tab';
}

function snapshotEditor( state ) {
	return JSON.stringify( {
		blockName: state.blockName,
		blockDescription: state.blockDescription || '',
		icon: state.icon,
		cssVars: state.cssVars,
		defaultAttrs: state.defaultAttrs || {},
		postTypes: [ ...( state.postTypes || [] ) ].sort(),
		tier: state.tier || '',
		packType: state.packType || '',
		category: state.category || '',
		categories: [ ...( state.categories || [] ) ].sort(),
	} );
}

export default function BlockEditor( {
	context,
	route,
	onBack,
	ownerMode = false,
} ) {
	const buttonLibrary = useButtonLibrary( {
		refreshOnMount: true,
		refreshOnFocus: true,
	} );
	const routePath = String( route || '' ).split( '?' )[ 0 ];
	const routeParts = routePath.split( '/' ).filter( Boolean );
	const idFromRoute = ownerMode ? 0 : parseInt( routeParts[ 1 ], 10 );
	const ownerSlug = ownerMode ? String( routeParts[ 1 ] || '' ) : '';
	const ownerVariant = ownerMode
		? String( routeParts[ 2 ] || 'default' )
		: 'default';
	const elFromRoute = useMemo( () => {
		const query = String( route || '' ).includes( '?' )
			? String( route ).slice( String( route ).indexOf( '?' ) + 1 )
			: '';
		if ( ! query ) {
			return null;
		}
		try {
			const el = new URLSearchParams( query ).get( 'el' );
			return el && String( el ).trim() ? String( el ).trim() : null;
		} catch ( e ) {
			return null;
		}
	}, [ route ] );
	const initialBlock = ownerMode
		? null
		: context?.userBlock || null;
	const [ block, setBlock ] = useState( initialBlock );
	const [ blockName, setBlockName ] = useState( () => initialBlock?.name || '' );
	const [ blockDescription, setBlockDescription ] = useState(
		() => initialBlock?.description || ''
	);
	const [ icon, setIcon ] = useState( () => initialBlock?.icon || 'screenoptions' );
	const [ cssVars, setCssVarsState ] = useState( () =>
		normalize( initialBlock?.cssVars || {} )
	);
	const [ defaultAttrs, setDefaultAttrsState ] = useState(
		() => initialBlock?.defaultAttrs || {}
	);
	const [ postTypes, setPostTypes ] = useState( () => {
		const types = initialBlock?.postTypes;
		return Array.isArray( types ) && types.length ? types : [ 'post', 'page' ];
	} );
	const [ packTier, setPackTier ] = useState(
		() => initialBlock?.tier || ( initialBlock?.isPro ? 'pro' : 'free' )
	);
	const [ packType, setPackType ] = useState( () =>
		initialBlock?.packType === 'variant' ? 'variant' : 'standalone'
	);
	const [ packCategories, setPackCategories ] = useState( () => {
		const list = Array.isArray( initialBlock?.categorySlugs )
			? initialBlock.categorySlugs
			: initialBlock?.categorySlug
				? [ initialBlock.categorySlug ]
				: [];
		return list.map( String ).filter( Boolean );
	} );
	const [ categoryLeaves, setCategoryLeaves ] = useState( [] );
	const [ panelPos, setPanelPos ] = useState( 'right' );
	const [ panelSize, setPanelSize ] = useState( 400 );
	const [ panelTab, setPanelTab ] = useState(
		ownerMode ? 'fields' : 'styles'
	);
	const [ viewport, setViewport ] = useState( 'desktop' );
	const [ selectedElementKey, setSelectedElementKey ] = useState( null );
	const [ focusSlideIndex, setFocusSlideIndex ] = useState( null );
	const [ focusCardIndex, setFocusCardIndex ] = useState( null );
	const [ focusButtonIndex, setFocusButtonIndex ] = useState( null );
	const [ addSlideLayout, setAddSlideLayout ] = useState( 'simple' );
	const [ designSubTab, setDesignSubTab ] = useState( 'items' );
	const [ saving, setSaving ] = useState( false );
	const [ savedFlash, setSavedFlash ] = useState( false );
	const [ availableTypes, setAvailableTypes ] = useState( [] );
	const [ baseline, setBaseline ] = useState( '' );
	const [ updateOpen, setUpdateOpen ] = useState( false );
	const [ loading, setLoading ] = useState( ! initialBlock );
	const [ loadError, setLoadError ] = useState( null );
	const dragRef = useRef( null );
	const skipHistoryRef = useRef( false );
	const documentStateRef = useRef( {
		defaultAttrs,
		cssVars,
	} );
	documentStateRef.current = { defaultAttrs, cssVars };

	const historyStorageKey = ownerMode
		? `owner:${ ownerSlug }:${ ownerVariant || 'default' }`
		: `user-block:${ idFromRoute || initialBlock?.id || 'new' }`;

	const applyDocumentState = useCallback( ( next ) => {
		skipHistoryRef.current = true;
		if ( next && typeof next === 'object' ) {
			if ( Object.prototype.hasOwnProperty.call( next, 'defaultAttrs' ) ) {
				setDefaultAttrsState( next.defaultAttrs || {} );
			}
			if ( Object.prototype.hasOwnProperty.call( next, 'cssVars' ) ) {
				setCssVarsState( normalize( next.cssVars || {} ) );
			}
		}
		skipHistoryRef.current = false;
	}, [] );

	const {
		trackSetter,
		clear: clearDocumentHistory,
		beginBatch,
		endBatch,
		cancelBatch,
		undo: undoDocument,
		redo: redoDocument,
	} = useDocumentHistory( {
		storageKey: `bm-block-editor:${ historyStorageKey }`,
		getState: () => documentStateRef.current,
		applyState: applyDocumentState,
	} );

	const setDefaultAttrs = useMemo( () => {
		const tracked = trackSetter( 'defaultAttrs', setDefaultAttrsState );
		return ( updater ) => {
			if ( skipHistoryRef.current ) {
				setDefaultAttrsState( updater );
				return;
			}
			tracked( updater );
		};
	}, [ trackSetter ] );

	const setCssVars = useMemo( () => {
		const tracked = trackSetter( 'cssVars', setCssVarsState );
		return ( updater ) => {
			if ( skipHistoryRef.current ) {
				setCssVarsState( updater );
				return;
			}
			tracked( updater );
		};
	}, [ trackSetter ] );

	const onContentEditSessionStart = useCallback( () => {
		skipHistoryRef.current = true;
		beginBatch( cloneState( documentStateRef.current ) );
	}, [ beginBatch ] );

	const onContentEditSessionCommit = useCallback(
		( _before, afterAttrs ) => {
			endBatch( {
				defaultAttrs: afterAttrs || documentStateRef.current.defaultAttrs,
				cssVars: documentStateRef.current.cssVars,
			} );
			skipHistoryRef.current = false;
		},
		[ endBatch ]
	);

	const onContentEditSessionEnd = useCallback( () => {
		cancelBatch();
		skipHistoryRef.current = false;
	}, [ cancelBatch ] );

	useEffect( () => {
		if ( ! elFromRoute ) {
			return;
		}
		setSelectedElementKey( elFromRoute );
		setPanelTab( 'styles' );
	}, [ elFromRoute ] );

	const isDirty = useMemo( () => {
		if ( ! baseline || ! block ) {
			return false;
		}
		return (
			snapshotEditor( {
				blockName,
				blockDescription: ownerMode ? blockDescription : '',
				icon,
				cssVars,
				defaultAttrs,
				postTypes,
				tier: ownerMode ? packTier : '',
				packType: ownerMode ? packType : '',
				category: ownerMode
					? packCategories[ 0 ] || block?.categorySlug || ''
					: '',
				categories: ownerMode ? packCategories : [],
			} ) !== baseline
		);
	}, [
		baseline,
		block,
		blockName,
		blockDescription,
		icon,
		cssVars,
		defaultAttrs,
		postTypes,
		ownerMode,
		packTier,
		packType,
		packCategories,
	] );

	useUnsavedChanges( isDirty );

	useEffect( () => {
		let cancelled = false;
		const load = async () => {
			skipHistoryRef.current = true;
			setLoadError( null );
			setLoading( true );
			try {
				if ( ownerMode ) {
					if ( ! ownerSlug ) {
						if ( ! cancelled ) {
							setBlock( null );
							setLoadError( {
								status: 404,
								code: 'jocbm_not_found',
								message: __(
									'This owner pack could not be found.',
									'joc-blockora-morph'
								),
							} );
						}
						return;
					}
					// Never flash a previous My Library / pack while fetching.
					setBlock( null );
					setBaseline( '' );
					const data = await ownerApi.get(
						`/owner/packs/${ ownerSlug }/${ ownerVariant || 'default' }`
					);
					if ( cancelled ) {
						return;
					}
					const shaped = {
						id: `owner:${ data.slug }:${ data.variant }`,
						name: data.name || data.title || '',
						icon: 'screenoptions',
						markup: data.markup || '',
						cssVars: data.cssVars || {},
						defaultAttrs: data.defaultAttrs || {},
						fieldSchema: data.fieldSchema || [],
						styleSchema: data.styleSchema || [],
						slug: data.slug,
						variant: data.variant,
						tier: data.tier === 'pro' ? 'pro' : 'free',
						publisherStatus: data.publisherStatus,
						packType:
							data.packType === 'variant' ? 'variant' : 'standalone',
						ownerPack: true,
						categorySlug: data.category,
						categorySlugs: Array.isArray( data.categories )
							? data.categories
							: data.category
								? [ data.category ]
								: [],
						description: data.description || '',
						isPro: data.tier === 'pro' || Boolean( data.isPro ),
						extends: data.extends || null,
					};
					setBlock( shaped );
					const nextName = shaped.name || '';
					const nextIcon = shaped.icon || 'screenoptions';
					const nextVars = normalize( shaped.cssVars || {} );
					const nextTypes = [ 'post', 'page' ];
					const nextTier = shaped.tier;
					const nextPackType = shaped.packType;
					const nextCats = shaped.categorySlugs || [];
					setBlockName( nextName );
					setBlockDescription( shaped.description || '' );
					setIcon( nextIcon );
					setCssVarsState( nextVars );
					setDefaultAttrsState( shaped.defaultAttrs || {} );
					setPostTypes( nextTypes );
					setPackTier( nextTier );
					setPackType( nextPackType );
					setPackCategories( nextCats );
					setBaseline(
						snapshotEditor( {
							blockName: nextName,
							blockDescription: shaped.description || '',
							icon: nextIcon,
							cssVars: nextVars,
							defaultAttrs: shaped.defaultAttrs || {},
							postTypes: nextTypes,
							tier: nextTier,
							packType: nextPackType,
							category: nextCats[ 0 ] || '',
							categories: nextCats,
						} )
					);
					clearDocumentHistory();
					return;
				}

				const id = Number( idFromRoute );
				if ( ! Number.isFinite( id ) || id <= 0 ) {
					if ( ! cancelled ) {
						setBlock( null );
						setBaseline( '' );
						setLoadError( {
							status: 404,
							code: 'jocbm_not_found',
							message: __(
								'This block could not be found. It may have been deleted, or the link is invalid.',
								'joc-blockora-morph'
							),
						} );
					}
					return;
				}
				// Avoid showing the previous block while the next one loads.
				if ( block?.id && Number( block.id ) !== id ) {
					setBlock( null );
					setBaseline( '' );
				}
				const data = await api.get( `/user-blocks/${ id }` );
				if ( cancelled ) {
					return;
				}
				setBlock( data );
				const nextName = data.name || '';
				const nextIcon = data.icon || 'screenoptions';
				const nextVars = normalize( data.cssVars || {} );
				const nextTypes =
					Array.isArray( data.postTypes ) && data.postTypes.length
						? data.postTypes
						: [ 'post', 'page' ];
				const nextCats = Array.isArray( data.categorySlugs )
					? data.categorySlugs
					: data.categorySlug
						? [ data.categorySlug ]
						: [];
				setBlockName( nextName );
				setIcon( nextIcon );
				setCssVarsState( nextVars );
				setDefaultAttrsState( data.defaultAttrs || {} );
				setPostTypes( nextTypes );
				setPackCategories( nextCats );
				setBaseline(
					snapshotEditor( {
						blockName: nextName,
						icon: nextIcon,
						cssVars: nextVars,
						defaultAttrs: data.defaultAttrs || {},
						postTypes: nextTypes,
					} )
				);
				clearDocumentHistory();
			} catch ( error ) {
				if ( cancelled ) {
					return;
				}
				const status = Number( error?.data?.status || error?.status || 0 );
				setBlock( null );
				setBaseline( '' );
				setLoadError( {
					status,
					code: String( error?.code || '' ),
					message:
						String( error?.message || '' ).trim() ||
						__(
							'Could not load this block.',
							'joc-blockora-morph'
						),
				} );
			} finally {
				if ( ! cancelled ) {
					setLoading( false );
					skipHistoryRef.current = false;
				}
			}
		};
		load();

		if ( ! ownerMode ) {
			api.get( '/post-types' )
				.then( ( res ) => {
					const items = Array.isArray( res?.items )
						? res.items
						: Array.isArray( res )
						? res
						: [];
					setAvailableTypes( items );
				} )
				.catch( () => {
					setAvailableTypes( [
						{ slug: 'post', label: __( 'Posts', 'joc-blockora-morph' ) },
						{ slug: 'page', label: __( 'Pages', 'joc-blockora-morph' ) },
					] );
				} );
		} else {
			api.get( '/categories' )
				.then( ( res ) => {
					const leaves = [];
					( res.tree || [] ).forEach( ( group ) => {
						( group.children || [] ).forEach( ( child ) => {
							if ( ! child?.slug ) {
								return;
							}
							leaves.push( {
								slug: child.slug,
								label: `${ group.name || group.slug } › ${
									child.name || child.slug
								}`,
							} );
						} );
					} );
					if ( ! leaves.length && Array.isArray( res.items ) ) {
						res.items.forEach( ( item ) => {
							if ( item?.parentSlug && item.slug ) {
								leaves.push( {
									slug: item.slug,
									label: item.name || item.slug,
								} );
							}
						} );
					}
					leaves.sort( ( a, b ) =>
						a.label.localeCompare( b.label, undefined, {
							sensitivity: 'base',
						} )
					);
					setCategoryLeaves( leaves );
				} )
				.catch( () => setCategoryLeaves( [] ) );
		}

		return () => {
			cancelled = true;
		};
		// eslint-disable-next-line react-hooks/exhaustive-deps -- load once per route key
	}, [ ownerMode, ownerSlug, ownerVariant, idFromRoute ] ); // eslint-disable-line react-hooks/exhaustive-deps — initial load only

	const reloadBlock = useCallback( async () => {
		const id = block?.id || idFromRoute;
		if ( ! id ) {
			return;
		}
		const data = await api.get( `/user-blocks/${ id }` );
		skipHistoryRef.current = true;
		setBlock( data );
		const nextName = data.name || '';
		const nextIcon = data.icon || 'screenoptions';
		const nextVars = normalize( data.cssVars || {} );
		const nextAttrs = data.defaultAttrs || {};
		const nextTypes = Array.isArray( data.postTypes ) && data.postTypes.length
			? data.postTypes
			: [ 'post', 'page' ];
		setBlockName( nextName );
		setIcon( nextIcon );
		setCssVarsState( nextVars );
		setDefaultAttrsState( nextAttrs );
		setPostTypes( nextTypes );
		setBaseline(
			snapshotEditor( {
				blockName: nextName,
				icon: nextIcon,
				cssVars: nextVars,
				defaultAttrs: nextAttrs,
				postTypes: nextTypes,
			} )
		);
		clearDocumentHistory();
		skipHistoryRef.current = false;
	}, [ block?.id, idFromRoute, clearDocumentHistory ] );

	const [ iconEpoch, setIconEpoch ] = useState( 0 );
	useEffect( () => {
		let cancelled = false;
		prefetchInlineIconsFromValue( defaultAttrs ).then( ( count ) => {
			if ( ! cancelled && count > 0 ) {
				setIconEpoch( ( n ) => n + 1 );
			}
		} );
		return () => {
			cancelled = true;
		};
	}, [ defaultAttrs ] );

	const preview = useMemo( () => {
		if ( ! block?.markup ) {
			return { __html: '', styleCss: '' };
		}
		// Wait until attrs are ready so tokens are not flashed as raw {{…}} text.
		if ( ! defaultAttrs || Object.keys( defaultAttrs ).length === 0 ) {
			return { __html: '', styleCss: '' };
		}
		return renderPreview(
			block.markup,
			defaultAttrs,
			cssVars,
			block.fieldSchema || [],
			viewport
		);
		// buttonLibrary: Style reference catalog — re-interpolate when it loads/updates.
		// iconEpoch: re-render after Lucide SVGs land in cache.
	}, [ block, cssVars, defaultAttrs, viewport, buttonLibrary, iconEpoch ] );

	const enabledFields = useMemo(
		() =>
			styleableFieldsInOrder(
				block?.fieldSchema || [],
				defaultAttrs?.elementsOrder,
				{
					defaultAttrs: block?.defaultAttrs || {},
					markup: block?.markup || '',
					breakpoint: viewport,
				}
			),
		[
			block?.fieldSchema,
			block?.defaultAttrs,
			block?.markup,
			defaultAttrs?.elementsOrder,
			viewport,
		]
	);

	const elementsOrderDefaults = useMemo(
		() =>
			elementsOrderDefaultsFromSchema(
				block?.fieldSchema || [],
				block?.defaultAttrs || {}
			),
		[ block?.fieldSchema, block?.defaultAttrs ]
	);

	const layoutMedia = useMemo(
		() =>
			layoutMediaFields(
				block?.fieldSchema || [],
				elementsOrderDefaults
			),
		[ block?.fieldSchema, elementsOrderDefaults ]
	);

	const sliderSettingsFields = useMemo(
		() =>
			( block?.fieldSchema || [] ).filter(
				( field ) =>
					field?.ui === 'styles' &&
					! field?.styleOnly &&
					field?.type !== 'elementsOrder'
			),
		[ block?.fieldSchema ]
	);

	const slides = useMemo(
		() =>
			Array.isArray( defaultAttrs?.slides ) ? defaultAttrs.slides : [],
		[ defaultAttrs?.slides ]
	);

	const isSliderBlock = useMemo(
		() =>
			( block?.fieldSchema || [] ).some(
				( field ) =>
					field?.key === 'slides' && field?.itemType === 'slide'
			),
		[ block?.fieldSchema ]
	);

	const hasSlideElements = isSliderBlock && enabledFields.length > 0;

	const collectionField = useMemo(
		() =>
			isSliderBlock
				? null
				: findCollectionRepeater( block?.fieldSchema || [] ),
		[ block?.fieldSchema, isSliderBlock ]
	);
	const collectionKey = collectionField?.key || null;
	// Pricing only: plans repeater (My Blocks keep a custom slug).
	const allowCollectionElementReorder =
		collectionField?.key === 'plans' ||
		collectionField?.itemType === 'plan' ||
		isPricingCollectionBlock( block );
	const collectionItems = useMemo( () => {
		if ( ! collectionKey ) {
			return [];
		}
		const raw = defaultAttrs?.[ collectionKey ];
		return Array.isArray( raw ) ? raw : [];
	}, [ defaultAttrs, collectionKey ] );
	const itemsTabLabel = useMemo(
		() => collectionItemsLabel( block || {}, collectionField || {} ),
		[ block, collectionField ]
	);
	const hasCollectionElements = Boolean( collectionField );

	useEffect( () => {
		if ( hasSlideElements ) {
			setDesignSubTab( 'slides' );
			return;
		}
		if ( hasCollectionElements ) {
			setDesignSubTab( 'items' );
		}
	}, [ hasSlideElements, hasCollectionElements, collectionKey ] );

	const selectableKeys = useMemo(
		() => enabledFields.map( ( f ) => f.key ),
		[ enabledFields ]
	);

	const selectedField = useMemo(
		() => enabledFields.find( ( f ) => f.key === selectedElementKey ) || null,
		[ enabledFields, selectedElementKey ]
	);

	const isSingleStyleable =
		enabledFields.length === 1 && ! hasSlideElements && ! hasCollectionElements;

	useEffect( () => {
		if ( selectedElementKey && ! selectedField ) {
			setSelectedElementKey( null );
		}
	}, [ selectedElementKey, selectedField ] );

	// Single-element blocks (e.g. Button) — open Design controls without a preview click.
	useEffect( () => {
		if ( ! isSingleStyleable ) {
			return;
		}
		const onlyKey = enabledFields[ 0 ]?.key;
		if ( onlyKey && selectedElementKey !== onlyKey ) {
			setSelectedElementKey( onlyKey );
		}
	}, [ enabledFields, isSingleStyleable, selectedElementKey ] );

	const activeVars = useMemo(
		() => effective( cssVars, viewport ),
		[ cssVars, viewport ]
	);

	const clearElementSelection = useCallback( () => {
		setSelectedElementKey( null );
		setFocusButtonIndex( null );
	}, [] );

	const onEditButtonStyles = useCallback( ( fieldKey, index ) => {
		setSelectedElementKey( fieldKey || 'buttons' );
		setFocusButtonIndex( typeof index === 'number' ? index : null );
		setPanelTab( 'styles' );
		scrollInspectorToTop();
	}, [] );

	const leaveSlide = useCallback( () => {
		setFocusSlideIndex( null );
		setSelectedElementKey( null );
		setFocusButtonIndex( null );
	}, [] );

	const openSlide = useCallback( ( slideIndex ) => {
		setDesignSubTab( 'slides' );
		setFocusSlideIndex( slideIndex );
		focusBmSliderSlide( slideIndex );
		setSelectedElementKey( null );
		setFocusButtonIndex( null );
	}, [] );

	const activeCard =
		focusCardIndex != null && collectionItems[ focusCardIndex ]
			? collectionItems[ focusCardIndex ]
			: null;

	const onElementSelect = useCallback(
		( key, context = null ) => {
			if ( context?.deselectElement ) {
				setSelectedElementKey( null );
				setFocusButtonIndex( null );
				return;
			}

			const slideIndex =
				context?.slideIndex != null &&
				Number.isFinite( Number( context.slideIndex ) )
					? Number( context.slideIndex )
					: null;
			const cardIndex =
				context?.cardIndex != null &&
				Number.isFinite( Number( context.cardIndex ) )
					? Number( context.cardIndex )
					: null;
			const buttonIndex =
				context?.buttonIndex != null &&
				Number.isFinite( Number( context.buttonIndex ) )
					? Number( context.buttonIndex )
					: null;
			const nextButtonFocus =
				key === 'buttons' ? buttonIndex : null;

			if ( slideIndex != null ) {
				setFocusSlideIndex( slideIndex );
				setFocusCardIndex( null );
				setDesignSubTab( 'slides' );
				setPanelTab( 'styles' );
				focusBmSliderSlide( slideIndex );
				setSelectedElementKey( key || null );
				setFocusButtonIndex( nextButtonFocus );
				return;
			}

			if ( cardIndex != null ) {
				setFocusCardIndex( cardIndex );
				setFocusSlideIndex( null );
				setDesignSubTab( 'items' );
				setPanelTab( 'styles' );
				setSelectedElementKey( key || null );
				setFocusButtonIndex( nextButtonFocus );
				return;
			}

			// Non-collection / hero-like blocks: select the style target by key.
		if ( key ) {
				setSelectedElementKey( key );
				setFocusButtonIndex( nextButtonFocus );
			setPanelTab( 'styles' );
				if ( collectionField ) {
					setDesignSubTab( 'items' );
				} else if ( hasSlideElements ) {
					setDesignSubTab( 'slides' );
				} else {
					setDesignSubTab( 'general' );
				}
				return;
			}

			// Empty canvas → clear selection; keep Items/Slides as active sub-tab.
			setSelectedElementKey( null );
			setFocusButtonIndex( null );
			setFocusSlideIndex( null );
			setFocusCardIndex( null );
			setPanelTab( 'styles' );
			if ( collectionField ) {
				setDesignSubTab( 'items' );
			} else if ( hasSlideElements ) {
				setDesignSubTab( 'slides' );
			} else {
				setDesignSubTab( 'general' );
			}
		},
		[ collectionField, hasSlideElements ]
	);

	const activeSlide =
		focusSlideIndex != null && slides[ focusSlideIndex ]
			? slides[ focusSlideIndex ]
			: null;

	const customizationContentField = useMemo( () => {
		if ( ! selectedElementKey || ! block?.fieldSchema ) {
			return null;
		}
		const schema = block.fieldSchema || [];
		const mergeTwinFlags = ( nested ) => {
			const top = schema.find(
				( field ) => field?.key === selectedElementKey
			);
			if ( ! top || ! nested ) {
				return nested;
			}
			return {
				...nested,
				disableAdvanceEditor:
					nested.disableAdvanceEditor ??
					nested.disableAdvancedEditor ??
					top.disableAdvanceEditor ??
					top.disableAdvancedEditor,
				allowedCommand:
					nested.allowedCommand ??
					nested.allowedCommands ??
					top.allowedCommand ??
					top.allowedCommands,
			};
		};
		if ( activeSlide ) {
			const slidesField = schema.find(
				( field ) =>
					field?.key === 'slides' && Array.isArray( field.fields )
			);
			const nested = ( slidesField?.fields || [] ).find(
				( field ) =>
					field?.key === selectedElementKey &&
					field?.type !== 'elementsOrder'
			);
			if ( nested ) {
				return mergeTwinFlags( nested );
			}
		}
		if ( activeCard && collectionField ) {
			const nested = ( collectionField.fields || [] ).find(
				( field ) =>
					field?.key === selectedElementKey &&
					field?.type !== 'elementsOrder'
			);
			if ( nested ) {
				return mergeTwinFlags( nested );
			}
			const top = schema.find(
				( field ) =>
					field?.key === selectedElementKey &&
					field?.type !== 'elementsOrder'
			);
			if ( top ) {
				return { ...top, styleOnly: false };
			}
		}
		const top = schema.find(
			( field ) =>
				field?.key === selectedElementKey &&
				field?.type !== 'elementsOrder'
		);
		if ( ! top ) {
			return null;
		}
		// Owner packs + My Blocks Builder: styleOnly twins are Design elsewhere —
		// sample/default content still lives in default_attrs, so unlock editing.
		return { ...top, styleOnly: false };
	}, [
		block?.fieldSchema,
		selectedElementKey,
		activeSlide,
		activeCard,
		collectionField,
	] );

	const setCustomizationContentAttrs = useCallback(
		( partial ) => {
			if ( activeSlide && focusSlideIndex != null ) {
				setDefaultAttrs( ( prev ) => {
					const list = Array.isArray( prev.slides ) ? prev.slides : [];
					return {
						...prev,
						slides: list.map( ( slide, i ) =>
							i === focusSlideIndex
								? { ...slide, ...partial }
								: slide
						),
					};
				} );
				return;
			}
			if ( collectionKey ) {
				setDefaultAttrs( ( prev ) => {
					const list = Array.isArray( prev[ collectionKey ] )
						? prev[ collectionKey ]
						: [];
					if ( ! list.length ) {
						return { ...prev, ...partial };
					}
					let idx =
						focusCardIndex != null && list[ focusCardIndex ]
							? Number( focusCardIndex )
							: null;
					if ( idx == null ) {
						const fromAttr = Number( prev.activeTab );
						idx =
							Number.isFinite( fromAttr ) && list[ fromAttr ]
								? fromAttr
								: 0;
					}
					const keys = Object.keys( partial || {} );
					const writeToItem =
						( focusCardIndex != null && list[ focusCardIndex ] ) ||
						keys.includes( 'icon' ) ||
						keys.includes( 'iconSize' ) ||
						keys.includes( 'iconColor' ) ||
						keys.some( ( k ) =>
							[
								'accordionTitle',
								'accordionContent',
								'tabTitle',
								'tabContent',
								'title',
								'header',
								'content',
								'text',
								'buttons',
								'link',
								'image',
							].includes( k )
						);
					if ( writeToItem && list[ idx ] ) {
						if (
							collectionSharesIcon( collectionField ) &&
							partialTouchesSharedIcon( partial )
						) {
							const shared = pickSharedIconPartial( partial );
							const rest = { ...( partial || {} ) };
							Object.keys( shared ).forEach( ( k ) => {
								delete rest[ k ];
							} );
							const hasRest = Object.keys( rest ).length > 0;
							return {
								...prev,
								[ collectionKey ]: list.map( ( item, i ) => {
									const next = { ...item, ...shared };
									return i === idx && hasRest
										? { ...next, ...rest }
										: next;
								} ),
							};
						}
						return {
							...prev,
							[ collectionKey ]: list.map( ( item, i ) =>
								i === idx ? { ...item, ...partial } : item
							),
						};
					}
					return { ...prev, ...partial };
				} );
				return;
			}
			setDefaultAttrs( ( prev ) => ( { ...prev, ...partial } ) );
		},
		[
			activeSlide,
			focusSlideIndex,
			focusCardIndex,
			collectionKey,
			collectionField,
		]
	);

	const setBlockLevelAttrs = useCallback( ( partial ) => {
		setDefaultAttrs( ( prev ) => ( { ...prev, ...partial } ) );
	}, [] );

	const customizationVisibility = useMemo( () => {
		const slidesField = ( block?.fieldSchema || [] ).find(
			( field ) =>
				field?.key === 'slides' && Array.isArray( field.fields )
		);
		const slidesOrderField =
			( slidesField?.fields || [] ).find(
				( field ) => field?.type === 'elementsOrder'
			) || null;
		const source =
			activeSlide ||
			( hasCollectionElements ? defaultAttrs : null ) ||
			defaultAttrs;
		const setter = activeSlide
			? setCustomizationContentAttrs
			: setBlockLevelAttrs;
		return contentFieldVisibility(
			{
				fieldSchema: block?.fieldSchema || [],
				defaultAttrs: block?.defaultAttrs || {},
			},
			source,
			setter,
			viewport,
			activeSlide
				? {
						orderField: slidesOrderField,
						defaultAttrs:
							( Array.isArray( block?.defaultAttrs?.slides ) &&
								block.defaultAttrs.slides[ 0 ] ) ||
							activeSlide,
				  }
				: {}
		);
	}, [
		block?.fieldSchema,
		block?.defaultAttrs,
		defaultAttrs,
		activeSlide,
		hasCollectionElements,
		setBlockLevelAttrs,
		setCustomizationContentAttrs,
		viewport,
	] );

	const isColumnsLayout = useMemo( () => {
		if ( activeSlide && hasSlideElements ) {
			return isColumnsSlideLayout( activeSlide.layout );
		}
		const fromVars = String(
			effective( cssVars, 'desktop' )[ '--bm-columns-breakpoint' ] ?? ''
		).trim();
		if ( fromVars ) {
			return true;
		}
		return ( block?.styleSchema || [] ).some( ( group ) =>
			( group?.fields || [] ).some(
				( field ) =>
					field?.key === '--bm-columns-breakpoint' ||
					field?.type === 'columnsBreakpoint'
			)
		);
	}, [ activeSlide, hasSlideElements, cssVars, block?.styleSchema ] );

	const activeMedia = useMemo( () => {
		if ( activeSlide && hasSlideElements ) {
			return activeSlide.image;
		}
		return defaultAttrs.image;
	}, [ activeSlide, hasSlideElements, defaultAttrs.image ] );

	const showMediaFullscreen =
		isColumnsLayout && mediaAllowsFullscreenFrame( activeMedia );

	const visibilityKeyBreakpoints = useMemo(
		() => columnMediaVisibilityKeyBreakpoints( isColumnsLayout ),
		[ isColumnsLayout ]
	);

	const customizationContentEditor = useMemo( () => {
		if ( ! customizationContentField ) {
			return null;
		}
		const slideFields =
			( block?.fieldSchema || [] ).find( ( f ) => f?.key === 'slides' )
				?.fields || null;
		return (
			<ContentFieldEditor
				field={ customizationContentField }
				attributes={ activeSlide || activeCard || defaultAttrs }
				setAttributes={ setCustomizationContentAttrs }
				visibilityKeys={ customizationVisibility.visibilityKeys }
				visibility={ customizationVisibility.visibility }
				visibilityBreakpoint={
					customizationVisibility.visibilityBreakpoint
				}
				onVisibilityChange={
					customizationVisibility.onVisibilityChange
				}
				visibilityKeyBreakpoints={ visibilityKeyBreakpoints }
				previewScope={ {
					cardIndex: activeCard ? focusCardIndex : null,
					slideIndex: activeSlide ? focusSlideIndex : null,
				} }
				fieldSchema={
					( activeSlide && slideFields ) ||
					( activeCard && collectionField?.fields ) ||
					block?.fieldSchema ||
					[]
				}
			/>
		);
	}, [
		customizationContentField,
		activeSlide,
		activeCard,
		focusCardIndex,
		focusSlideIndex,
		collectionField,
		customizationVisibility,
		visibilityKeyBreakpoints,
		block?.fieldSchema,
		defaultAttrs,
		setCustomizationContentAttrs,
	] );

	const toolbarButtons = activeSlide
		? activeSlide.buttons
		: activeCard
			? activeCard.buttons
			: defaultAttrs.buttons;

	const canRemoveSlide = slides.length > MIN_HERO_SLIDES;

	const isEditingCollectionItem =
		selectedField?.elementType === 'tabLabel' ||
		selectedElementKey === 'tabTitle' ||
		selectedElementKey === 'accordionTitle';

	const removeCardIndex = useMemo( () => {
		if ( ! collectionKey || ! collectionItems.length ) {
			return null;
		}
		if (
			focusCardIndex != null &&
			collectionItems[ focusCardIndex ]
		) {
			return Number( focusCardIndex );
		}
		// Customize → title without a canvas-focused row: use active tab / first.
		if ( isEditingCollectionItem ) {
			const fromAttr = Number( defaultAttrs?.activeTab );
			if (
				Number.isFinite( fromAttr ) &&
				collectionItems[ fromAttr ]
			) {
				return fromAttr;
			}
			return 0;
		}
		return null;
	}, [
		collectionKey,
		collectionItems,
		focusCardIndex,
		isEditingCollectionItem,
		defaultAttrs?.activeTab,
	] );

	const collectionChromeActionsAllowed = collectionItemChromeActionsAllowed(
		collectionField || {},
		selectedElementKey
	);
	const canRemoveFocusedCard =
		collectionChromeActionsAllowed &&
		removeCardIndex != null &&
		collectionItems.length >
			collectionMinItems( collectionField || {} );

	const canDuplicateFocusedCard =
		collectionChromeActionsAllowed &&
		removeCardIndex != null &&
		Boolean( collectionKey );
	const canReorderFocusedCard =
		collectionChromeActionsAllowed &&
		removeCardIndex != null &&
		Boolean( collectionKey ) &&
		collectionItems.length > 1;

	const removeCardLabel = String(
		( removeCardIndex != null
			? collectionItems[ removeCardIndex ]
			: null
		)?.tabTitle ||
			( removeCardIndex != null
				? collectionItems[ removeCardIndex ]
				: null
			)?.accordionTitle ||
			( removeCardIndex != null
				? collectionItems[ removeCardIndex ]
				: null
			)?.title ||
			( removeCardIndex != null
				? collectionItems[ removeCardIndex ]
				: null
			)?.header ||
			( removeCardIndex != null
				? collectionItems[ removeCardIndex ]
				: null
			)?.tag ||
			( removeCardIndex != null
				? collectionItems[ removeCardIndex ]
				: null
			)?.price ||
			''
	)
		.replace( /<[^>]+>/g, ' ' )
		.replace( /\s+/g, ' ' )
		.trim();

	const removeItemConfirmCopy = useMemo(
		() => collectionRemoveConfirmCopy( collectionField ),
		[ collectionField ]
	);
	const removeItemConfirmDescription = removeItemConfirmCopy.description(
		removeCardLabel || removeItemConfirmCopy.fallbackName
	);

	const removeFocusedCard = () => {
		if ( ! canRemoveFocusedCard || removeCardIndex == null ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		setSelectedElementKey( null );
		const removedIndex = Number( removeCardIndex );
		setDefaultAttrs( ( prev ) => {
			const list = Array.isArray( prev[ collectionKey ] )
				? prev[ collectionKey ]
				: [];
			const nextItems = list.filter( ( _, i ) => i !== removedIndex );
			const next = { ...prev, [ collectionKey ]: nextItems };
			const active = Number( prev.activeTab );
			if ( Number.isFinite( active ) ) {
				if ( ! nextItems.length ) {
					next.activeTab = 0;
				} else if ( active >= nextItems.length || active === removedIndex ) {
					next.activeTab = Math.min(
						removedIndex,
						nextItems.length - 1
					);
				} else if ( active > removedIndex ) {
					next.activeTab = active - 1;
				}
			}
			return next;
		} );
		if ( ! collectionItems.length || collectionItems.length <= 1 ) {
			setFocusCardIndex( null );
			setSelectedElementKey( null );
			return;
		}
		const nextFocus = Math.min(
			removedIndex,
			collectionItems.length - 2
		);
		setFocusCardIndex( nextFocus );
	};

	const duplicateFocusedCard = () => {
		if ( ! canDuplicateFocusedCard || removeCardIndex == null || ! collectionKey ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		setSelectedElementKey( null );
		const sourceIndex = Number( removeCardIndex );
		setDefaultAttrs( ( prev ) => {
			const result = duplicateCollectionItemAt(
				prev,
				collectionKey,
				sourceIndex
			);
			if ( ! result ) {
				return prev;
			}
			const next = { ...prev, ...result.patch };
			const active = Number( prev.activeTab );
			if ( Number.isFinite( active ) && active > sourceIndex ) {
				next.activeTab = active + 1;
			}
			return next;
		} );
		setFocusCardIndex( sourceIndex + 1 );
		setSelectedElementKey( null );
		setDesignSubTab( 'items' );
		setPanelTab( 'styles' );
	};

	const reorderFocusedCard = ( from, to ) => {
		if ( ! canReorderFocusedCard || ! collectionKey ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		setSelectedElementKey( null );
		setDefaultAttrs( ( prev ) => {
			const result = moveCollectionItemAt(
				prev,
				collectionKey,
				from,
				to
			);
			if ( ! result ) {
				return prev;
			}
			return { ...prev, ...result.patch };
		} );
		setFocusCardIndex( Number( to ) );
	};

	const addSlide = () => {
		setDefaultAttrs( ( prev ) => {
			const list = Array.isArray( prev.slides ) ? prev.slides : [];
			const next = [
				...list,
				createDefaultHeroSlide( { layout: addSlideLayout } ),
			];
			setDesignSubTab( 'slides' );
			setFocusSlideIndex( next.length - 1 );
			focusBmSliderSlide( next.length - 1 );
			setSelectedElementKey( null );
			return { ...prev, slides: next };
		} );
	};
	const removeSlide = ( slideIndex ) => {
		if ( ! canRemoveSlide ) {
			return;
		}
		setDefaultAttrs( ( prev ) => {
			const list = Array.isArray( prev.slides ) ? prev.slides : [];
			return {
				...prev,
				slides: list.filter( ( _, i ) => i !== slideIndex ),
			};
		} );
		if ( focusSlideIndex == null ) {
			return;
		}
		if ( focusSlideIndex === slideIndex ) {
			leaveSlide();
			return;
		}
		if ( focusSlideIndex > slideIndex ) {
			setFocusSlideIndex( focusSlideIndex - 1 );
		}
	};

	const reorderSlides = ( nextSlides, { from, to } = {} ) => {
		setDefaultAttrs( ( prev ) => ( {
			...prev,
			slides: nextSlides,
		} ) );
		if ( from == null || to == null ) {
			return;
		}
		const nextFocus = remapFocusSlideIndex( focusSlideIndex, from, to );
		if ( nextFocus !== focusSlideIndex ) {
			setFocusSlideIndex( nextFocus );
		}
		if ( nextFocus != null ) {
			focusBmSliderSlide( nextFocus );
		}
	};

	const onVarChange = ( key, value, forceBreakpoint ) => {
		setCssVars( ( prev ) =>
			forceBreakpoint === 'desktop'
				? setSharedDesktopVar( prev, key, value )
				: setBreakpointVar( prev, viewport, key, value )
		);
	};

	const onVarsPatch = ( patch, forceBreakpoint ) => {
		setCssVars( ( prev ) => {
			let next = prev;
			Object.entries( patch || {} ).forEach( ( [ key, value ] ) => {
				next =
					forceBreakpoint === 'desktop'
						? setSharedDesktopVar( next, key, value )
						: setBreakpointVar( next, viewport, key, value );
			} );
			return next;
		} );
	};

	const updateActiveSlideCssVars = ( updater ) => {
		if ( focusSlideIndex == null || ! activeSlide ) {
			return;
		}
		const hydrated = ensureSlideLayoutCssVars( activeSlide, cssVars );
		const nextVars = updater( hydrated );
		setDefaultAttrs( ( prev ) => {
			const list = Array.isArray( prev.slides ) ? prev.slides : [];
			return {
				...prev,
				slides: list.map( ( slide, i ) =>
					i === focusSlideIndex
						? { ...slide, cssVars: nextVars }
						: slide
				),
			};
		} );
	};

	const onSlideVarChange = ( key, value, forceBreakpoint ) => {
		updateActiveSlideCssVars( ( prev ) =>
			forceBreakpoint === 'desktop'
				? setSharedDesktopVar( prev, key, value )
				: setBreakpointVar( prev, viewport, key, value )
		);
	};

	const onSlideVarsPatch = ( patch, forceBreakpoint ) => {
		updateActiveSlideCssVars( ( prev ) => {
			let next = prev;
			Object.entries( patch || {} ).forEach( ( [ key, value ] ) => {
				next =
					forceBreakpoint === 'desktop'
						? setSharedDesktopVar( next, key, value )
						: setBreakpointVar( next, viewport, key, value );
			} );
			return next;
		} );
	};

	const activeSlideLayoutValues = useMemo(
		() =>
			activeSlide
				? effectiveSlideLayoutValues( activeSlide, cssVars, viewport )
				: {},
		[ activeSlide, cssVars, viewport ]
	);

	const mediaFrameValue = useMemo( () => {
		if ( activeSlide && hasSlideElements && isColumnsLayout ) {
			return (
				activeSlideLayoutValues[ '--bm-media-frame' ] ??
				activeVars[ '--bm-media-frame' ] ??
				'framed'
			);
		}
		return activeVars[ '--bm-media-frame' ] ?? 'framed';
	}, [
		activeSlide,
		hasSlideElements,
		isColumnsLayout,
		activeSlideLayoutValues,
		activeVars,
	] );

	const activeSlideStyleGroups = useMemo( () => {
		if ( ! activeSlide ) {
			return [];
		}
		return slideStyleGroupsFor( activeSlide.layout, viewport );
	}, [ activeSlide, viewport ] );

	const onMediaFrameChange = ( next ) => {
		if ( activeSlide && hasSlideElements && isColumnsLayout ) {
			onSlideVarChange( '--bm-media-frame', next );
			return;
		}
		onVarChange( '--bm-media-frame', next );
	};

	useEffect( () => {
		if (
			! isColumnsLayout ||
			mediaAllowsFullscreenFrame( activeMedia ) ||
			! isMediaFullscreen( mediaFrameValue )
		) {
			return;
		}
		onMediaFrameChange( 'framed' );
		// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [ isColumnsLayout, activeMedia, mediaFrameValue ] );

	const toggleType = ( slug ) => {
		setPostTypes( ( prev ) =>
			prev.includes( slug ) ? prev.filter( ( s ) => s !== slug ) : [ ...prev, slug ]
		);
	};

	const save = async () => {
		if ( ! block || saving ) {
			return;
		}
		const trimmed = blockName.trim();
		if ( ! trimmed ) {
			window.alert( __( 'Block name is required.', 'joc-blockora-morph' ) );
			setPanelTab( 'settings' );
			return;
		}
		setSaving( true );
		try {
			if ( ownerMode ) {
				const slug = block.slug || ownerSlug;
				const variant = block.variant || ownerVariant || 'default';
				const cats = packCategories.length
					? packCategories
					: [ block.categorySlug || 'misc' ];
				const updated = await ownerApi.put(
					`/owner/packs/${ slug }/${ variant }`,
					{
						name: trimmed,
						description: blockDescription.trim(),
						category: cats[ 0 ] || 'misc',
						categories: cats,
						tier: packTier === 'pro' ? 'pro' : 'free',
						cssVars,
						defaultAttrs,
						publisherStatus: block.publisherStatus || 'draft',
						packType:
							packType === 'standalone' ? 'standalone' : 'variant',
						isFeatured: Boolean( block.isFeatured ),
						isHidden: Boolean( block.isHidden ),
						isPro: packTier === 'pro',
					}
				);
				const nextTier =
					updated.tier === 'pro' || updated.isPro ? 'pro' : 'free';
				const nextPackType =
					updated.packType === 'variant' ? 'variant' : 'standalone';
				const shaped = {
					...block,
					name: updated.name,
					description: updated.description,
					cssVars: updated.cssVars,
					defaultAttrs: updated.defaultAttrs,
					markup: updated.markup,
					fieldSchema: updated.fieldSchema,
					styleSchema: updated.styleSchema,
					publisherStatus: updated.publisherStatus,
					categorySlug: updated.category,
					categorySlugs: Array.isArray( updated.categories )
						? updated.categories
						: updated.category
							? [ updated.category ]
							: [],
					tier: nextTier,
					isPro: nextTier === 'pro',
					packType: nextPackType,
					extends: updated.extends || block.extends || null,
				};
				setBlock( shaped );
				setBlockName( shaped.name );
				setBlockDescription( shaped.description || '' );
				setPackTier( nextTier );
				setPackType( nextPackType );
				const savedCats = shaped.categorySlugs || [];
				setPackCategories( savedCats );
				const nextAttrs = shaped.defaultAttrs || defaultAttrs;
				skipHistoryRef.current = true;
				setDefaultAttrsState( nextAttrs );
				const nextVars = normalize( shaped.cssVars || {} );
				setCssVarsState( nextVars );
				skipHistoryRef.current = false;
				clearDocumentHistory();
				setBaseline(
					snapshotEditor( {
						blockName: shaped.name,
						blockDescription: shaped.description || '',
						icon,
						cssVars: nextVars,
						defaultAttrs: nextAttrs,
						postTypes,
						tier: nextTier,
						packType: nextPackType,
						category: savedCats[ 0 ] || '',
						categories: savedCats,
					} )
				);
				setSavedFlash( true );
				window.setTimeout( () => setSavedFlash( false ), 2200 );
			} else {
			const updated = await api.put( `/user-blocks/${ block.id }`, {
				cssVars,
				defaultAttrs,
				postTypes,
				name: trimmed,
				icon,
			} );
			setBlock( updated );
			setBlockName( updated.name );
			setIcon( updated.icon || 'screenoptions' );
			const nextAttrs = updated.defaultAttrs || defaultAttrs;
			skipHistoryRef.current = true;
			setDefaultAttrsState( nextAttrs );
			const nextVars = normalize( updated.cssVars || {} );
			setCssVarsState( nextVars );
			skipHistoryRef.current = false;
			clearDocumentHistory();
			syncLocalizedLibraryCssVars( block.id, nextVars, nextAttrs );
			setBaseline(
				snapshotEditor( {
					blockName: updated.name,
					icon: updated.icon || 'screenoptions',
					cssVars: nextVars,
					defaultAttrs: nextAttrs,
					postTypes,
				} )
			);
			setSavedFlash( true );
			window.setTimeout( () => setSavedFlash( false ), 2200 );
				await refreshButtonLibrary();
			}
		} catch ( e ) {
			window.alert( e?.message || __( 'Save failed.', 'joc-blockora-morph' ) );
		}
		setSaving( false );
	};

	useSaveShortcut( save );

	const onResizeStart = ( e ) => {
		e.preventDefault();
		const start = panelPos === 'bottom' ? e.clientY : e.clientX;
		const startSize = panelSize;

		const onMove = ( ev ) => {
			const delta =
				panelPos === 'bottom'
					? start - ev.clientY
					: panelPos === 'left'
					? ev.clientX - start
					: start - ev.clientX;
			setPanelSize( Math.min( 640, Math.max( 300, startSize + delta ) ) );
		};
		const onUp = () => {
			window.removeEventListener( 'mousemove', onMove );
			window.removeEventListener( 'mouseup', onUp );
		};
		window.addEventListener( 'mousemove', onMove );
		window.addEventListener( 'mouseup', onUp );
	};

	if ( loading && ! block ) {
		return (
			<BrandLoader
				label={ __( 'Loading editor…', 'joc-blockora-morph' ) }
			/>
		);
	}

	if ( loadError || ! block ) {
		const is404 =
			Number( loadError?.status ) === 404 ||
			loadError?.code === 'jocbm_not_found';
		const missingId =
			! ownerMode && Number.isFinite( Number( idFromRoute ) )
				? Number( idFromRoute )
				: 0;
		const title = is404
			? __( 'Block not found', 'joc-blockora-morph' )
			: __( 'Could not load block', 'joc-blockora-morph' );
		const detail =
			loadError?.message ||
			__(
				'This block could not be found. It may have been deleted, or the link is invalid.',
				'joc-blockora-morph'
			);
		return (
			<div className="bm-editor-shell flex h-screen flex-col font-sans text-on-background antialiased">
				<header className="bm-editor-topbar bm-glass flex shrink-0 items-center gap-3 px-4 py-2.5 sm:px-5">
					<div className="bm-editor-topbar__start">
						<button
							type="button"
							className="bm-editor-topbar__brand"
							onClick={ onBack }
							aria-label={ __(
								'Gutenberg Blocks',
								'joc-blockora-morph'
							) }
						>
							<img
								className="bm-editor-topbar__logo"
								src={ brandLogoUrl() }
								alt=""
							/>
						</button>
					</div>
					<div className="bm-editor-topbar__actions ml-auto">
						<button
							type="button"
							className="bm-btn bm-btn--ghost bm-btn--sm"
							onClick={ onBack }
						>
							←{ ' ' }
							{ ownerMode
								? __( 'Back', 'joc-blockora-morph' )
								: __(
										'Gutenberg Blocks',
										'joc-blockora-morph'
								  ) }
						</button>
					</div>
				</header>
				<main className="flex flex-1 items-center justify-center p-6">
					<div
						className="bm-empty bm-empty--center"
						role="alert"
						aria-live="polite"
					>
						<span
							className={
								'bm-empty__icon dashicons ' +
								( is404
									? 'dashicons-warning'
									: 'dashicons-dismiss' )
							}
							aria-hidden="true"
						/>
						<p className="m-0 text-caps-micro font-bold uppercase tracking-widest text-outline">
							{ is404
								? '404'
								: sprintf(
										/* translators: %d: HTTP status code */
										__( 'Error %d', 'joc-blockora-morph' ),
										Number( loadError?.status ) || 0
								  ) }
						</p>
						<h1 className="bm-empty__title">{ title }</h1>
						<p className="bm-empty__text">{ detail }</p>
						{ missingId > 0 ? (
							<p className="bm-empty__text m-0 text-on-surface-variant">
								{ sprintf(
									/* translators: %d: missing block ID */
									__( 'Requested ID: %d', 'joc-blockora-morph' ),
									missingId
								) }
							</p>
						) : null }
						<button
							type="button"
							className="bm-btn bm-btn--primary"
							onClick={ onBack }
						>
							{ ownerMode
								? __( 'Go back', 'joc-blockora-morph' )
								: __(
										'Back to Gutenberg Blocks',
										'joc-blockora-morph'
								  ) }
						</button>
					</div>
				</main>
			</div>
		);
	}

	const styleSchema = ( block.styleSchema || [] ).filter( ( group ) => {
		if ( ! hasSlideElements ) {
			return true;
		}
		return ! isSlideLevelStyleGroup( group?.group );
	} );
	const panelStyle =
		panelPos === 'bottom' ? { height: panelSize } : { width: panelSize };

	const bodyClass =
		panelPos === 'bottom'
			? 'bm-editor-body is-bottom'
			: panelPos === 'left'
			? 'bm-editor-body is-left'
			: 'bm-editor-body is-right';

	return (
		<div className="bm-editor-shell flex h-screen flex-col font-sans text-on-background antialiased">
			<header className="bm-editor-topbar bm-glass flex shrink-0 items-center gap-3 px-4 py-2.5 sm:px-5">
				<div className="bm-editor-topbar__start">
					<button
						type="button"
						className="bm-editor-topbar__brand"
						onClick={ onBack }
						aria-label={ __( 'Gutenberg Blocks', 'joc-blockora-morph' ) }
					>
						<img
							className="bm-editor-topbar__logo"
							src={ brandLogoUrl() }
							alt=""
							width={ 44 }
							height={ 44 }
							decoding="async"
						/>
					</button>
				</div>

				<div className="bm-editor-topbar__title min-w-0 flex-1">
					<p className="m-0 text-caps-micro font-bold uppercase tracking-widest text-outline">
						{ ownerMode
							? __( 'Owner pack', 'joc-blockora-morph' )
							: __( 'Gutenberg block', 'joc-blockora-morph' ) }
					</p>
					<h1 className="m-0 flex items-center gap-2 truncate text-headline-md text-on-surface">
						<BlockIdentityIcon
							icon={ icon }
							className="bm-dashicon--title shrink-0 text-primary"
						/>
						<span className="truncate">{ blockName || block.name }</span>
					</h1>
					<button
						type="button"
						className="bm-editor-topbar__back"
						onClick={ onBack }
					>
						<span
							className="dashicons dashicons-arrow-left-alt2"
							aria-hidden="true"
						/>
						{ ownerMode
							? __( 'Owner Library', 'joc-blockora-morph' )
							: __( 'Gutenberg Blocks', 'joc-blockora-morph' ) }
					</button>
				</div>

				{ block.updateAvailable ? (
					<button
						type="button"
						className="bm-btn bm-btn--ghost bm-btn--sm"
						onClick={ () => setUpdateOpen( true ) }
					>
						<span
							className="dashicons dashicons-update text-[16px]"
							aria-hidden="true"
						/>
						{ __( 'Review update', 'joc-blockora-morph' ) }
					</button>
				) : null }

					<button
						type="button"
						className={
							savedFlash
							? 'bm-btn bm-btn--primary is-saved'
							: isDirty
							? 'bm-btn bm-btn--primary is-dirty'
							: 'bm-btn bm-btn--primary'
						}
						onClick={ save }
						disabled={ saving }
					>
						<span
							className={
								savedFlash
									? 'dashicons dashicons-yes-alt text-[16px]'
									: 'dashicons dashicons-cloud-saved text-[16px]'
							}
							aria-hidden="true"
						/>
						{ saving
							? __( 'Saving…', 'joc-blockora-morph' )
							: savedFlash
							? __( 'Saved', 'joc-blockora-morph' )
							: ownerMode
							? __( 'Save pack', 'joc-blockora-morph' )
							: __( 'Save block', 'joc-blockora-morph' ) }
					</button>

				<div
					className="bm-seg-tabs bm-seg-tabs--3 bm-seg-tabs--toolbar bm-editor-pos"
					role="group"
					aria-label={ __( 'Inspector position', 'joc-blockora-morph' ) }
				>
							{ POSITIONS.map( ( pos ) => (
								<button
									key={ pos.id }
									type="button"
									className={
										panelPos === pos.id
									? 'bm-seg-tab is-active'
									: 'bm-seg-tab'
							}
							onClick={ () => setPanelPos( pos.id ) }
							aria-pressed={ panelPos === pos.id }
							aria-label={ pos.label }
							title={ pos.label }
								>
									<span
										className={ `dashicons dashicons-${ pos.icon }` }
										aria-hidden="true"
									/>
								</button>
							) ) }
						</div>
			</header>

			<div className={ bodyClass }>
				<div className="bm-editor-canvas relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
					<PreviewFrame
						html={ preview }
						className="min-h-0 flex-1 border-0"
						frameClassName="min-h-[240px]"
						viewport={ viewport }
						onViewportChange={ setViewport }
						minHeight={ 320 }
						selectableKeys={ selectableKeys }
						selectedElementKey={ selectedElementKey }
						focusSlideIndex={ focusSlideIndex }
						focusCardIndex={ focusCardIndex }
						focusButtonIndex={ focusButtonIndex }
						onElementSelect={ onElementSelect }
						onRemoveItem={
							canRemoveFocusedCard ? removeFocusedCard : undefined
						}
						canRemoveItem={ canRemoveFocusedCard }
						onDuplicateItem={
							canDuplicateFocusedCard
								? duplicateFocusedCard
								: undefined
						}
						canDuplicateItem={ canDuplicateFocusedCard }
						onReorderItem={
							canReorderFocusedCard
								? reorderFocusedCard
								: undefined
						}
						canReorderItem={ canReorderFocusedCard }
						removeItemConfirmTitle={ removeItemConfirmCopy.title }
						removeItemConfirmLabel={
							removeItemConfirmCopy.confirmLabel
						}
						removeItemConfirmDescription={
							removeItemConfirmDescription
						}
						contentEditEnabled
						fieldSchema={ block?.fieldSchema || [] }
						attributes={ defaultAttrs }
						setAttributes={ setBlockLevelAttrs }
						onContentEditSessionStart={ onContentEditSessionStart }
						onContentEditSessionCommit={ onContentEditSessionCommit }
						onContentEditSessionEnd={ onContentEditSessionEnd }
						onDocumentUndo={ undoDocument }
						onDocumentRedo={ redoDocument }
						stageNotice={
							ownerMode ? (
								<div
									className="bm-layout-notice"
									role="status"
									aria-live="polite"
								>
									<span
										className="bm-layout-notice__icon"
										aria-hidden="true"
									>
										<span className="dashicons dashicons-edit" />
									</span>
									<div className="bm-layout-notice__copy">
										<strong className="bm-layout-notice__title">
											{ __(
												'Pack defaults',
												'joc-blockora-morph'
											) }
										</strong>
										<p className="bm-layout-notice__text">
											{ __(
												'Edit sample content in Fields. What you save here becomes the default when this pack is published to the library.',
												'joc-blockora-morph'
											) }
										</p>
									</div>
								</div>
							) : (
							<div
								className="bm-layout-notice"
								role="status"
								aria-live="polite"
							>
								<span
									className="bm-layout-notice__icon"
									aria-hidden="true"
								>
									<span className="dashicons dashicons-edit" />
								</span>
								<div className="bm-layout-notice__copy">
									<strong className="bm-layout-notice__title">
										{ __(
											'My Block source',
											'joc-blockora-morph'
										) }
									</strong>
									<p className="bm-layout-notice__text">
										{ __(
											'Fields and Design here are the live My Block source. Linked placements in Gutenberg and on the frontend stay in sync until someone chooses Customize this instance.',
											'joc-blockora-morph'
										) }
									</p>
								</div>
							</div>
							)
						}
					/>
				</div>

				<div
					className={
						panelPos === 'bottom'
							? 'bm-editor-resizer is-horizontal'
							: 'bm-editor-resizer is-vertical'
					}
					ref={ dragRef }
					onMouseDown={ onResizeStart }
					role="separator"
					aria-orientation={ panelPos === 'bottom' ? 'horizontal' : 'vertical' }
				/>

				<aside className="bm-editor-panel flex shrink-0 flex-col overflow-hidden" style={ panelStyle }>
					<div className="bm-editor-panel__tabs" role="tablist">
						<button
							type="button"
							role="tab"
								aria-selected={ panelTab === 'fields' }
								className={ tabClass( panelTab === 'fields' ) }
								onClick={ () => setPanelTab( 'fields' ) }
						>
							<span
									className="dashicons dashicons-edit"
								aria-hidden="true"
							/>
								{ __( 'Fields', 'joc-blockora-morph' ) }
						</button>
							<button
								type="button"
								role="tab"
							aria-selected={ panelTab === 'styles' }
							className={ tabClass( panelTab === 'styles' ) }
							onClick={ () => setPanelTab( 'styles' ) }
							>
								<span
								className="dashicons dashicons-art"
									aria-hidden="true"
								/>
							{ __( 'Customize', 'joc-blockora-morph' ) }
							</button>
						<button
							type="button"
							role="tab"
							aria-selected={ panelTab === 'settings' }
							className={ tabClass( panelTab === 'settings' ) }
							onClick={ () => setPanelTab( 'settings' ) }
						>
							<span
								className="dashicons dashicons-admin-generic"
								aria-hidden="true"
							/>
							{ __( 'Advanced', 'joc-blockora-morph' ) }
						</button>
					</div>

					<div className="custom-scrollbar bm-editor-panel__body flex min-w-0 max-w-full flex-1 flex-col space-y-5 overflow-x-clip overflow-y-auto">
						{ panelTab === 'fields' ? (
							<>
								<p className="m-0 text-body-md text-on-surface-variant">
									{ ownerMode
										? __(
												'Sample content saved with this pack becomes the default when published to the library.',
												'joc-blockora-morph'
										  )
										: __(
												'Edit sample text, images, and items for this My Block. New inserts use these values; existing Gutenberg and frontend placements keep their own content.',
												'joc-blockora-morph'
										  ) }
								</p>
								<FieldControls
									meta={ {
										fieldSchema: block?.fieldSchema || [],
										defaultAttrs: block?.defaultAttrs || {},
									} }
									attributes={ defaultAttrs }
									setAttributes={ setBlockLevelAttrs }
									breakpoint={ viewport }
									allowManageButtonItems
									onEditButtonStyles={ onEditButtonStyles }
									onFocusCardIndex={ setFocusCardIndex }
								/>
							</>
						) : null }

						{ panelTab === 'styles' && (
							<>
								<ViewportEditBanner viewport={ viewport } />
								{ selectedField ? (
									<ElementToolbar
										field={ selectedField }
										values={ activeVars }
										onVarChange={ onVarChange }
										onVarsPatch={ onVarsPatch }
										breakpoint={ viewport }
										onBack={
											isSingleStyleable
												? undefined
												: clearElementSelection
										}
										buttonLibrary={ buttonLibrary }
										buttons={ toolbarButtons }
										focusButtonIndex={ focusButtonIndex }
										elementVisible={ isElementVisible(
											selectedField.key,
											activeSlide ||
												activeCard ||
												defaultAttrs,
											viewport
										) }
										showMediaFullscreen={ showMediaFullscreen }
										mediaFrameValue={ mediaFrameValue }
										onMediaFrameChange={ onMediaFrameChange }
										contentEditor={ customizationContentEditor }
										onButtonsChange={ ( next ) => {
											if (
												activeSlide &&
												focusSlideIndex != null
											) {
												setDefaultAttrs( ( prev ) => {
													const list = Array.isArray(
														prev.slides
													)
														? prev.slides
														: [];
													return {
														...prev,
														slides: list.map(
															( slide, i ) =>
																i ===
																focusSlideIndex
																	? {
																			...slide,
																			buttons: next,
																	  }
																	: slide
														),
													};
												} );
												return;
											}
											if (
												activeCard &&
												focusCardIndex != null &&
												collectionKey
											) {
												setDefaultAttrs( ( prev ) => {
													const list = Array.isArray(
														prev[ collectionKey ]
													)
														? prev[ collectionKey ]
														: [];
													return {
														...prev,
														[ collectionKey ]:
															list.map(
																( item, i ) =>
																	i ===
																	focusCardIndex
																		? {
																				...item,
																				buttons: next,
																		  }
																		: item
															),
													};
												} );
												return;
											}
											setDefaultAttrs( ( prev ) => ( {
												...prev,
												buttons: next,
											} ) );
										} }
										contentAttrs={
											selectedField?.key ===
												'accordionArrow' ||
											selectedField?.elementType ===
												'accordionArrow'
												? defaultAttrs
												: activeSlide ||
												  ( removeCardIndex != null
														? collectionItems[
																removeCardIndex
														  ]
														: null ) ||
												  activeCard ||
												  defaultAttrs
										}
										onContentAttrsChange={
											selectedField?.key ===
												'accordionArrow' ||
											selectedField?.elementType ===
												'accordionArrow'
												? setBlockLevelAttrs
												: setCustomizationContentAttrs
										}
										sharedIcon={ collectionSharesIcon(
											collectionField
										) }
										slideIndex={
											hasSlideElements
												? focusSlideIndex
												: null
										}
										onRemoveCard={
											canRemoveFocusedCard
												? removeFocusedCard
												: undefined
										}
										canRemoveCard={ canRemoveFocusedCard }
										removeCardLabel={ removeCardLabel }
									/>
								) : (
									<>
										<p className="m-0 text-body-md text-on-surface-variant">
											{ hasSlideElements
												? designSubTab === 'slides'
													? __(
															'Open a slide to style its elements. Autoplay pauses and jumps to that slide.',
															'joc-blockora-morph'
													  )
													: __(
															'Background, spacing, and slider chrome apply to the whole hero. Layout is per slide under Slides.',
															'joc-blockora-morph'
													  )
												: hasCollectionElements
												? designSubTab === 'items'
													? __(
															'Pick an element to style all items the same way. Edit item content in the Fields tab — samples for new inserts only.',
															'joc-blockora-morph'
													  )
													: __(
															'General styles apply to the whole block. Shared element styles are under the Items tab.',
															'joc-blockora-morph'
													  )
												: __(
														'Click an element in the preview or pick one below to style it for this viewport.',
												'joc-blockora-morph'
											) }
										</p>
										<div className="bm-inspector-stack">
										{ hasSlideElements || hasCollectionElements ? (
											<div
												className="bm-design-subtabs"
												role="tablist"
												aria-label={ __(
													'Customization sections',
													'joc-blockora-morph'
												) }
											>
												{ hasSlideElements ? (
													<button
														type="button"
														role="tab"
														aria-selected={ designSubTab === 'slides' }
														className={
															'bm-design-subtabs__btn' +
															( designSubTab === 'slides' ? ' is-active' : '' )
														}
														onClick={ () => setDesignSubTab( 'slides' ) }
													>
														{ __( 'Slides', 'joc-blockora-morph' ) }
													</button>
												) : null }
												{ hasCollectionElements ? (
													<button
														type="button"
														role="tab"
														aria-selected={ designSubTab === 'items' }
														className={
															'bm-design-subtabs__btn' +
															( designSubTab === 'items' ? ' is-active' : '' )
														}
														onClick={ () => setDesignSubTab( 'items' ) }
													>
														{ itemsTabLabel }
													</button>
												) : null }
												<button
													type="button"
													role="tab"
													aria-selected={ designSubTab === 'general' }
													className={
														'bm-design-subtabs__btn' +
														( designSubTab === 'general' ? ' is-active' : '' )
													}
													onClick={ () => setDesignSubTab( 'general' ) }
												>
													{ __( 'General', 'joc-blockora-morph' ) }
												</button>
											</div>
										) : null }

										{ ( ( ! hasSlideElements && ! hasCollectionElements ) ||
											designSubTab === 'general' ) &&
										sliderSettingsFields.length > 0 ? (
											<section className="bm-inspector-card">
												<strong className="bm-inspector-card__title">
													{ __(
														'Slider',
														'joc-blockora-morph'
													) }
												</strong>
												{ sliderSettingsFields.map(
													( field ) => (
														<SchemaFieldControl
															key={ field.key }
															field={ field }
															value={
																defaultAttrs[
																	field.key
																]
															}
															onChange={ (
																next
															) =>
																setDefaultAttrs(
																	(
																		prev
																	) => ( {
																		...prev,
																		[ field.key ]:
																			next,
																	} )
																)
															}
														/>
													)
												) }
											</section>
										) : null }

										{ hasSlideElements &&
										designSubTab === 'slides' ? (
											<section className="bm-inspector-card bm-gutenberg-elements is-open">
												{ focusSlideIndex != null &&
												activeSlide ? (
													<div className="bm-gutenberg-elements__slide-nav">
														<Button
															variant="tertiary"
															className="bm-gutenberg-elements__back"
															icon="arrow-left-alt2"
															onClick={ leaveSlide }
														>
															{ __(
																'Slides',
																'joc-blockora-morph'
															) }
														</Button>
														<div className="bm-gutenberg-elements__slide-nav-title">
															<strong className="bm-inspector-card__title">
																{ sprintf(
																	/* translators: %d: slide number */
																	__(
																		'Slide %d',
																		'joc-blockora-morph'
																	),
																	focusSlideIndex +
																		1
																) }
															</strong>
															<span className="bm-gutenberg-elements__slide-layout">
																{ slideLayoutLabel(
																	activeSlide.layout
																) }
															</span>
														</div>
														{ canRemoveSlide ? (
															<Button
																size="small"
																variant="tertiary"
																isDestructive
																className="bm-gutenberg-elements__slide-remove"
																icon="no-alt"
																aria-label={ __(
																	'Remove slide',
																	'joc-blockora-morph'
																) }
																onClick={ () =>
																	removeSlide(
																		focusSlideIndex
																	)
																}
															/>
														) : null }
													</div>
												) : (
													<strong className="bm-inspector-card__title">
														{ __(
															'Slides',
															'joc-blockora-morph'
														) }
													</strong>
												) }
												<div className="bm-gutenberg-elements__list">
													{ focusSlideIndex != null &&
													activeSlide ? (
														<>
															<strong className="bm-gutenberg-elements__section-label">
																{ __(
																	'Elements',
																	'joc-blockora-morph'
																) }
															</strong>
															<p className="bm-gutenberg-elements__order-hint">
																{ sprintf(
																	/* translators: %s: desktop, tablet, or mobile */
																	__(
														'Drag to a drop line between items for %s. Each viewport can have its own order.',
														'joc-blockora-morph'
																	),
																	viewport ===
																		'tablet'
																		? __(
																				'Tablet',
																				'joc-blockora-morph'
																		  )
																		: viewport ===
																		  'mobile'
																		? __(
																				'Mobile',
																				'joc-blockora-morph'
																		  )
																		: __(
																				'Desktop',
																				'joc-blockora-morph'
																		  )
																) }
															</p>
															<div className="bm-gutenberg-elements__sortable">
																<SlideElementsList
																	styleable={
																		enabledFields
																	}
																	slide={
																		activeSlide
																	}
																	attributes={
																		activeSlide ||
																		defaultAttrs
																	}
																	breakpoint={
																		viewport
																	}
																	orderDefaults={ slideOrderDefaults(
																		activeSlide?.layout
																	) }
																	listKey={ String(
																		focusSlideIndex
																	) }
																	selectedKey={
																		selectedElementKey
																	}
																	onSelect={ (
																		fieldKey
																	) => {
																		setDesignSubTab(
																			'slides'
																		);
																		focusBmSliderSlide(
																			focusSlideIndex
																		);
																		onElementSelect(
																			fieldKey,
																			{
																				slideIndex:
																					focusSlideIndex,
																			}
																		);
																	} }
																	onReorder={ (
																		nextOrder
																	) => {
																		const orderDefaults =
																			slideOrderDefaults(
																				activeSlide?.layout
																			);
																		setDefaultAttrs(
																			(
																				prev
																			) => {
																				const list =
																					Array.isArray(
																						prev.slides
																					)
																						? prev.slides
																						: [];
																				return {
																					...prev,
																					slides: list.map(
																						(
																							slide,
																							i
																						) =>
																							i ===
																							focusSlideIndex
																								? {
																										...slide,
																										elementsOrder:
																											serializeElementsOrder(
																												setElementsOrderAtBreakpoint(
																													slide.elementsOrder,
																													viewport,
																													nextOrder,
																													orderDefaults
																												)
																											),
																								  }
																								: slide
																					),
																				};
																			}
																		);
																	} }
																/>
															</div>
															{ activeSlideStyleGroups.length ? (
																<div className="bm-gutenberg-elements__slide-layout-styles">
																	{ activeSlideStyleGroups.map(
																		(
																			group
																		) => (
																			<div
																				key={
																					group.group
																				}
																				className="bm-gutenberg-elements__slide-style-group"
																			>
																				<strong className="bm-gutenberg-elements__section-label">
																					{
																						group.group
																					}
																				</strong>
																				<StyleFieldList
																					fields={
																						group.fields
																					}
																					values={
																						activeSlideLayoutValues
																					}
																					onVarChange={
																						onSlideVarChange
																					}
																					onVarsPatch={
																						onSlideVarsPatch
																					}
																					breakpoint={
																						viewport
																					}
																				/>
																			</div>
																		)
																	) }
																</div>
															) : null }
														</>
													) : (
														<>
															<SlidesList
																slides={ slides }
																onOpen={ openSlide }
																onRemove={
																	removeSlide
																}
																onReorder={
																	reorderSlides
																}
															/>
															<div className="bm-gutenberg-elements__add-slide-row">
																<SelectControl
																	label={ __(
																		'Hero layout',
																		'joc-blockora-morph'
																	) }
																	value={
																		addSlideLayout
																	}
																	options={ SLIDE_LAYOUT_OPTIONS.map(
																		(
																			opt
																		) => ( {
																			value: opt.value,
																			label: __(
																				opt.label,
																				'joc-blockora-morph'
																			),
																		} )
																	) }
																	onChange={ (
																		next
																	) =>
																		setAddSlideLayout(
																			next ||
																				'simple'
																		)
																	}
																	__nextHasNoMarginBottom
																	__next40pxDefaultSize
																/>
																<Button
																	variant="secondary"
																	className="bm-gutenberg-elements__add-slide"
																	icon="plus-alt2"
																	onClick={
																		addSlide
																	}
																>
																	{ __(
																		'Add slide',
																		'joc-blockora-morph'
																	) }
																</Button>
															</div>
														</>
													) }
												</div>
											</section>
										) : null }

										{ hasCollectionElements &&
										designSubTab === 'items' ? (
											<section className="bm-inspector-card bm-gutenberg-elements is-open">
												<strong className="bm-inspector-card__title">
													{ __( 'Elements', 'joc-blockora-morph' ) }
												</strong>
												<div className="bm-gutenberg-elements__list">
													{ enabledFields.length > 0 ? (
														<>
															<p className="bm-gutenberg-elements__order-hint">
																{ ownerMode
																	? __(
																			'Styles apply to every item. Add or edit item content in the Fields tab — saved as published defaults.',
																			'joc-blockora-morph'
																	  )
																	: __(
																			'Styles apply to every item. Add or edit item content in the Fields tab — samples for new inserts; placements keep their own copy.',
																			'joc-blockora-morph'
																	  ) }
															</p>
															{ allowCollectionElementReorder ? (
																<p className="bm-gutenberg-elements__order-hint">
																	{ sprintf(
																		/* translators: %s: desktop, tablet, or mobile */
																		__(
																			'Drag to a drop line between items for %s. Each viewport can have its own order.',
																			'joc-blockora-morph'
																		),
																		viewport === 'tablet'
																			? __(
																					'Tablet',
																					'joc-blockora-morph'
																			  )
																			: viewport === 'mobile'
																			? __(
																					'Mobile',
																					'joc-blockora-morph'
																			  )
																			: __(
																					'Desktop',
																					'joc-blockora-morph'
																			  )
																	) }
																</p>
															) : null }
															<div
																className={
																	allowCollectionElementReorder
																		? 'bm-gutenberg-elements__sortable'
																		: 'bm-gutenberg-elements__list-static'
																}
															>
																<SlideElementsList
																	fields={ enabledFields }
																	attributes={ defaultAttrs }
																	breakpoint={ viewport }
																	elementsOrder={ defaultAttrs.elementsOrder }
																	orderDefaults={ elementsOrderDefaults }
																	listKey="collection"
																	selectedKey={ selectedElementKey }
																	staticKeys={
																		allowCollectionElementReorder
																			? [ 'button' ]
																			: undefined
																	}
																	allowReorder={
																		allowCollectionElementReorder
																	}
																	onSelect={ ( fieldKey ) => {
																		setDesignSubTab( 'items' );
																		setSelectedElementKey( fieldKey );
																	} }
																	onReorder={
																		allowCollectionElementReorder
																			? ( nextOrder ) => {
																					setDefaultAttrs(
																						( prev ) => ( {
																							...prev,
																							elementsOrder:
																								serializeElementsOrder(
																									setElementsOrderAtBreakpoint(
																										prev.elementsOrder,
																										viewport,
																										nextOrder,
																										elementsOrderDefaults
																									)
																								),
																						} )
																					);
																			  }
																			: undefined
																	}
																/>
															</div>
														</>
													) : (
														<p className="bm-gutenberg-elements__order-hint">
															{ __(
																'No styleable elements. Use General for block styles, or Fields to manage items.',
																'joc-blockora-morph'
															) }
														</p>
													) }
												</div>
											</section>
										) : null }

										{ ! hasSlideElements &&
										! hasCollectionElements &&
										enabledFields.length > 0 ? (
											<section className="bm-inspector-card bm-gutenberg-elements is-open">
												<strong className="bm-inspector-card__title">
													{ __(
														'Elements',
														'joc-blockora-morph'
													) }
												</strong>
												<p className="bm-gutenberg-elements__order-hint">
													{ sprintf(
														/* translators: %s: desktop, tablet, or mobile */
														__(
														'Drag to a drop line between items for %s. Each viewport can have its own order.',
														'joc-blockora-morph'
														),
														viewport === 'tablet'
															? __(
																	'Tablet',
																	'joc-blockora-morph'
															  )
															: viewport ===
															  'mobile'
															? __(
																	'Mobile',
																	'joc-blockora-morph'
															  )
															: __(
																	'Desktop',
																	'joc-blockora-morph'
															  )
													) }
												</p>
												<div className="bm-gutenberg-elements__list">
													<div className="bm-gutenberg-elements__sortable">
														<SlideElementsList
															fields={ enabledFields.filter(
																( field ) =>
																	elementsOrderDefaults.includes(
																		field.key
																	)
															) }
															attributes={
																defaultAttrs
															}
															breakpoint={
																viewport
															}
															elementsOrder={
																defaultAttrs.elementsOrder
															}
															orderDefaults={
																elementsOrderDefaults
															}
															listKey="block"
															selectedKey={
																selectedElementKey
															}
															onSelect={ (
																fieldKey
															) =>
																onElementSelect(
																	fieldKey
																)
															}
															onReorder={ (
																nextOrder
															) => {
																setDefaultAttrs(
																	( prev ) => ( {
																		...prev,
																		elementsOrder:
																			serializeElementsOrder(
																				setElementsOrderAtBreakpoint(
																					prev.elementsOrder,
																					viewport,
																					nextOrder,
																					elementsOrderDefaults
																				)
																			),
																	} )
																);
															} }
														/>
													</div>
													{ enabledFields.some(
														( field ) =>
															! elementsOrderDefaults.includes(
																field.key
															)
													) ? (
														<div className="bm-gutenberg-elements__list-static">
															{ enabledFields
																.filter(
																	( field ) =>
																		! elementsOrderDefaults.includes(
																			field.key
																		)
																)
																.map(
																	(
																		field,
																		extraIndex
																	) => {
																		const visible =
																			isElementVisible(
																				field.key,
																				defaultAttrs,
																				viewport
																			);
																		const index =
																			enabledFields.filter(
																				(
																					f
																				) =>
																					elementsOrderDefaults.includes(
																						f.key
																					)
																			)
																				.length +
																			extraIndex +
																			1;
																		return (
																			<button
																				key={
																					field.key
																				}
																				type="button"
																				aria-current={
																					selectedElementKey ===
																					field.key
																						? 'true'
																						: undefined
																				}
																				className={
																					'bm-gutenberg-element-btn' +
																					( visible
																						? ''
																						: ' is-element-hidden' ) +
																					( selectedElementKey ===
																					field.key
																						? ' is-selected'
																						: '' )
																				}
																				onClick={ () =>
																					onElementSelect(
																						field.key
																					)
																				}
																			>
																				<span
																					className="bm-gutenberg-element-btn__index"
																					aria-hidden="true"
																				>
																					{
																						index
																					}
																				</span>
																				<span className="bm-gutenberg-element-btn__label">
																					{ field.label ||
																						field.key }
																				</span>
																				{ visible ? null : (
																					<span className="bm-gutenberg-element-btn__badge">
																						{ sprintf(
																							/* translators: %s: Desktop, Tablet, or Mobile */
																							__(
																								'Hidden · %s',
																								'joc-blockora-morph'
																							),
																							viewport ===
																								'tablet'
																								? __(
																										'Tablet',
																										'joc-blockora-morph'
																								  )
																								: viewport ===
																								  'mobile'
																								? __(
																										'Mobile',
																										'joc-blockora-morph'
																								  )
																								: __(
																										'Desktop',
																										'joc-blockora-morph'
																								  )
																						) }
																					</span>
																				) }
																				<span className="bm-gutenberg-element-btn__action">
																					{ __(
																						'Edit',
																						'joc-blockora-morph'
																					) }
																					<span
																						className="dashicons dashicons-arrow-right-alt2"
																						aria-hidden="true"
																					/>
																				</span>
																			</button>
																		);
																	}
																) }
														</div>
													) : null }
												</div>
											</section>
										) : null }

										{ ( ( ! hasSlideElements && ! hasCollectionElements ) ||
											designSubTab === 'general' ) &&
										layoutMedia.length > 0 ? (
											<section className="bm-inspector-card">
												<strong className="bm-inspector-card__title">
													{ __(
														'Media',
														'joc-blockora-morph'
													) }
												</strong>
												{ layoutMedia.map( ( field ) => (
													<SchemaFieldControl
														key={ field.key }
														field={ field }
														value={
															defaultAttrs[
																field.key
															]
														}
														onChange={ ( next ) =>
															setBlockLevelAttrs( {
																[ field.key ]:
																	next,
															} )
														}
													/>
												) ) }
											</section>
										) : null }

										{ ( ( ! hasSlideElements && ! hasCollectionElements ) ||
											designSubTab === 'general' ) &&
											styleSchema.map( ( group ) => {
												const fields = (
													group.fields || []
												).filter( ( f ) =>
													isStyleFieldVisibleAtBreakpoint(
														f,
														viewport
													)
											);
											if ( ! fields.length ) {
												return null;
											}
											return (
											<section
												key={ group.group }
												className="bm-inspector-card"
											>
												<strong className="bm-inspector-card__title">
													{ group.group }
												</strong>
												<StyleFieldList
													fields={ fields }
															values={
																activeVars
															}
															onVarChange={
																onVarChange
															}
															onVarsPatch={
																onVarsPatch
															}
															breakpoint={
																viewport
															}
												/>
											</section>
											);
										} ) }
										</div>
									</>
								) }
							</>
						) }

						{ panelTab === 'settings' && (
							<>
								<section className="space-y-3">
									<h3 className="m-0 text-headline-md text-on-surface">
										{ __( 'Gutenberg identity', 'joc-blockora-morph' ) }
									</h3>
									<p className="m-0 text-body-md text-on-surface-variant">
										{ ownerMode
											? __(
													'This name and description appear on the library card. Names must be unique.',
													'joc-blockora-morph'
											  )
											: __(
													'This name and icon appear in the block inserter. Names must be unique.',
													'joc-blockora-morph'
											  ) }
									</p>
									<label className="block">
										<span className="bm-field-label">
											{ __( 'Block name', 'joc-blockora-morph' ) }{ ' ' }
											<em className="not-italic text-red-600">*</em>
										</span>
										<input
											type="text"
											className="bm-field-input"
											value={ blockName }
											required
											onChange={ ( e ) => setBlockName( e.target.value ) }
										/>
									</label>
									{ ownerMode ? (
										<label className="block">
											<span className="bm-field-label">
												{ __(
													'Description',
													'joc-blockora-morph'
												) }
											</span>
											<textarea
												className="bm-field-input"
												rows={ 3 }
												value={ blockDescription }
												onChange={ ( e ) =>
													setBlockDescription(
														e.target.value
													)
												}
												placeholder={ __(
													'Short summary shown in the library card.',
													'joc-blockora-morph'
												) }
											/>
										</label>
									) : null }
									{ ! ownerMode ? (
									<IconPicker
										label={ __( 'Icon', 'joc-blockora-morph' ) }
										value={ icon }
										onChange={ setIcon }
									/>
									) : null }
								</section>
								{ ownerMode ? (
							<section className="space-y-3">
								<h3 className="m-0 text-headline-md text-on-surface">
											{ __(
												'Pack distribution',
												'joc-blockora-morph'
											) }
								</h3>
										<p className="m-0 text-body-md text-on-surface-variant">
											{ __(
												'Free packs write to the free plugin templates. Pro packs write under JOC Blockora Morph Pro.',
												'joc-blockora-morph'
											) }
										</p>
										<div
											className="flex flex-col gap-2"
											role="radiogroup"
											aria-label={ __(
												'Free or Pro pack',
												'joc-blockora-morph'
											) }
										>
											<label className="flex items-start gap-2 text-body-md text-on-surface">
												<input
													type="radio"
													name="joc-bm-owner-tier"
													value="free"
													checked={ packTier === 'free' }
													onChange={ () =>
														setPackTier( 'free' )
													}
												/>
												<span>
													<strong>
														{ __(
															'Free',
															'joc-blockora-morph'
														) }
													</strong>
													<br />
													<span className="text-on-surface-variant">
														{ __(
															'joc-blockora-morph/templates',
															'joc-blockora-morph'
														) }
													</span>
												</span>
											</label>
											<label className="flex items-start gap-2 text-body-md text-on-surface">
												<input
													type="radio"
													name="joc-bm-owner-tier"
													value="pro"
													checked={ packTier === 'pro' }
													onChange={ () =>
														setPackTier( 'pro' )
													}
												/>
												<span>
													<strong>
														{ __(
															'Pro',
															'joc-blockora-morph'
														) }
													</strong>
													<br />
													<span className="text-on-surface-variant">
														{ __(
															'joc-blockora-morph-pro/templates',
															'joc-blockora-morph'
														) }
													</span>
												</span>
											</label>
										</div>
									</section>
								) : null }
								{ ownerMode ? (
									<section className="space-y-3">
										<h3 className="m-0 text-headline-md text-on-surface">
											{ __(
												'Library appearance',
												'joc-blockora-morph'
											) }
										</h3>
										<p className="m-0 text-body-md text-on-surface-variant">
											{ __(
												'Variants appear only as choices inside the parent block’s Library preview. Stand alone Templates get their own Library card.',
												'joc-blockora-morph'
											) }
										</p>
										<div
											className="flex flex-col gap-2"
											role="radiogroup"
											aria-label={ __(
												'Variant or stand alone template',
												'joc-blockora-morph'
											) }
										>
											<label className="flex items-start gap-2 text-body-md text-on-surface">
												<input
													type="radio"
													name="joc-bm-owner-pack-type"
													value="variant"
													checked={
														packType === 'variant'
													}
													onChange={ () =>
														setPackType( 'variant' )
													}
												/>
												<span>
													<strong>
														{ __(
															'Variant',
															'joc-blockora-morph'
														) }
													</strong>
													<br />
													<span className="text-on-surface-variant">
														{ __(
															'Shown in the parent block’s preview layers only.',
															'joc-blockora-morph'
														) }
													</span>
												</span>
											</label>
											<label className="flex items-start gap-2 text-body-md text-on-surface">
												<input
													type="radio"
													name="joc-bm-owner-pack-type"
													value="standalone"
													checked={
														packType ===
														'standalone'
													}
													onChange={ () =>
														setPackType(
															'standalone'
														)
													}
												/>
												<span>
													<strong>
														{ __(
															'Stand alone Template',
															'joc-blockora-morph'
														) }
													</strong>
													<br />
													<span className="text-on-surface-variant">
														{ __(
															'Own card in the JBM Library grid.',
															'joc-blockora-morph'
														) }
													</span>
												</span>
											</label>
										</div>
									</section>
								) : null }
								{ ownerMode ? (
									<section className="space-y-3">
										<h3 className="m-0 text-headline-md text-on-surface">
											{ __(
												'Library categories',
												'joc-blockora-morph'
											) }
										</h3>
										<p className="m-0 text-body-md text-on-surface-variant">
											{ __(
												'Pick one or more library categories. The first selected is the primary badge; the pack appears in every selected filter.',
												'joc-blockora-morph'
											) }
										</p>
										<div
											className="flex max-h-56 flex-col gap-2 overflow-y-auto custom-scrollbar"
											role="group"
											aria-label={ __(
												'Library categories',
												'joc-blockora-morph'
											) }
										>
											{ categoryLeaves.length === 0 ? (
												<p className="m-0 text-body-md text-on-surface-variant">
													{ __(
														'Loading categories…',
														'joc-blockora-morph'
													) }
												</p>
											) : (
												categoryLeaves.map( ( leaf ) => {
													const checked =
														packCategories.includes(
															leaf.slug
														);
													const isPrimary =
														packCategories[ 0 ] ===
														leaf.slug;
													return (
														<label
															key={ leaf.slug }
															className="flex items-start gap-2 text-body-md text-on-surface"
														>
															<input
																type="checkbox"
																checked={
																	checked
																}
																onChange={ () => {
																	setPackCategories(
																		( prev ) => {
																			if (
																				prev.includes(
																					leaf.slug
																				)
																			) {
																				return prev.filter(
																					(
																						s
																					) =>
																						s !==
																						leaf.slug
																				);
																			}
																			return [
																				...prev,
																				leaf.slug,
																			];
																		}
																	);
																} }
															/>
															<span>
																{ leaf.label }
																{ isPrimary ? (
																	<>
																		{ ' ' }
																		<span className="text-on-surface-variant">
																			{ __(
																				'(primary)',
																				'joc-blockora-morph'
																			) }
																		</span>
																	</>
																) : null }
															</span>
														</label>
													);
												} )
											) }
										</div>
										{ packCategories.length > 1 ? (
											<label className="block">
												<span className="bm-field-label">
													{ __(
														'Primary category',
														'joc-blockora-morph'
													) }
												</span>
												<select
													className="bm-field-input"
													value={
														packCategories[ 0 ] ||
														''
													}
													onChange={ ( e ) => {
														const primary =
															e.target.value;
														setPackCategories(
															( prev ) => {
																const rest =
																	prev.filter(
																		( s ) =>
																			s !==
																			primary
																	);
																return [
																	primary,
																	...rest,
																];
															}
														);
													} }
												>
													{ packCategories.map(
														( slug ) => {
															const leaf =
																categoryLeaves.find(
																	( l ) =>
																		l.slug ===
																		slug
																);
															return (
																<option
																	key={
																		slug
																	}
																	value={
																		slug
																	}
																>
																	{ leaf?.label ||
																		slug }
																</option>
															);
														}
													) }
												</select>
											</label>
										) : null }
									</section>
								) : packCategories.length ? (
									<section className="space-y-3">
										<h3 className="m-0 text-headline-md text-on-surface">
											{ __(
												'Library categories',
												'joc-blockora-morph'
											) }
										</h3>
										<p className="m-0 text-body-md text-on-surface-variant">
											{ __(
												'Inherited from the library template. Categories cannot be changed on My Blocks.',
												'joc-blockora-morph'
											) }
										</p>
										<ul className="m-0 list-none space-y-1 p-0">
											{ packCategories.map( ( slug ) => (
												<li
													key={ slug }
													className="text-body-md text-on-surface"
												>
													{ slug }
													{ slug ===
													packCategories[ 0 ]
														? ` · ${ __(
																'primary',
																'joc-blockora-morph'
														  ) }`
														: '' }
												</li>
											) ) }
										</ul>
									</section>
								) : null }
								{ ! ownerMode ? (
								<section className="space-y-3">
									<h3 className="m-0 text-headline-md text-on-surface">
										{ __( 'Post types', 'joc-blockora-morph' ) }
									</h3>
									<p className="m-0 text-body-md text-on-surface-variant">
										{ __(
											'Show this block only in selected Gutenberg editors.',
											'joc-blockora-morph'
										) }
									</p>
										{ ( Array.isArray( availableTypes )
											? availableTypes
											: []
										).map( ( type ) => (
										<label
											key={ type.slug }
											className="flex items-center gap-2 text-body-md text-on-surface"
										>
											<input
												type="checkbox"
													checked={
														Array.isArray( postTypes ) &&
														postTypes.includes( type.slug )
													}
												onChange={ () => toggleType( type.slug ) }
											/>
											{ type.label }
										</label>
									) ) }
								</section>
								) : null }
							</>
						) }
					</div>
				</aside>
			</div>
			<FloatingToast
				visible={ savedFlash }
				message={ __( 'Changes saved', 'joc-blockora-morph' ) }
			/>
			{ updateOpen ? (
				<TemplateUpdateModal
					blockId={ block.id }
					blockName={ blockName || block.name }
					onClose={ () => setUpdateOpen( false ) }
					onApplied={ () => {
						setUpdateOpen( false );
						reloadBlock().catch( () => {} );
					} }
				/>
			) : null }
		</div>
	);
}
