import { createPortal, useEffect, useMemo, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { api } from '../api';
import BlocksListFilters, {
	categoryOptionsFromItems,
	filterBlocksList,
	postTypeOptionsFromItems,
	blockCategorySlugs,
	blockMatchesCategory,
} from '../components/BlocksListFilters';
import PreviewFrame from '../components/PreviewFrame';
import LibraryCardThumb from '../components/LibraryCardThumb';
import LibrarySkeleton from '../components/LibrarySkeleton';
import TemplateUpdateModal from '../components/TemplateUpdateModal';
import CloneBlockModal from '../components/CloneBlockModal';
import { renderPreview } from '../components/PreviewModal';
import ConfirmModal from '../components/ui/ConfirmModal';
import BulkConfirmItemsList from '../components/BulkConfirmItemsList';
import { refreshButtonLibrary } from '../../shared/buttonLibrary';
import { blockDashicon } from '../../shared/blockDashicon';

const BROWSE_MODE_KEY = 'joc-blockora-morph-gb-browse-mode';
const ACTIVE_CATEGORY_KEY = 'joc-blockora-morph-gb-active-category';

function loadBrowseMode() {
	try {
		const raw = window.localStorage?.getItem( BROWSE_MODE_KEY );
		return raw === 'list' ? 'list' : 'categories';
	} catch ( e ) {
		return 'categories';
	}
}

function loadActiveCategory() {
	try {
		const raw = window.localStorage?.getItem( ACTIVE_CATEGORY_KEY );
		const slug = String( raw || '' ).trim();
		return slug || null;
	} catch ( e ) {
		return null;
	}
}

function persistActiveCategory( slug ) {
	try {
		if ( slug ) {
			window.localStorage?.setItem( ACTIVE_CATEGORY_KEY, slug );
		} else {
			window.localStorage?.removeItem( ACTIVE_CATEGORY_KEY );
		}
	} catch ( e ) {
		// ignore
	}
}

function BlockPreviewModal( { block, onClose, onCustomize } ) {
	const [ viewport, setViewport ] = useState( 'desktop' );
	const preview = useMemo(
		() =>
			block
				? renderPreview(
						block.markup,
						block.defaultAttrs,
						block.cssVars,
						block.fieldSchema || [],
						viewport
				  )
				: { __html: '', styleCss: '' },
		[ block, viewport ]
	);

	useEffect( () => {
		if ( ! block ) {
			return undefined;
		}
		const onKey = ( e ) => {
			if ( e.key === 'Escape' ) {
				onClose();
			}
		};
		const prevOverflow = document.body.style.overflow;
		document.body.style.overflow = 'hidden';
		window.addEventListener( 'keydown', onKey );
		return () => {
			document.body.style.overflow = prevOverflow;
			window.removeEventListener( 'keydown', onKey );
		};
	}, [ block, onClose ] );

	if ( ! block ) {
		return null;
	}

	const portalTarget =
		document.getElementById( 'joc-blockora-morph-app' ) || document.body;

	return createPortal(
		<div
			className="bm-modal-root bm-fade fixed inset-0 z-[100000] flex items-center justify-center p-4 font-sans sm:p-6"
			role="dialog"
			aria-modal="true"
			aria-labelledby="bm-block-preview-title"
		>
			<div className="bm-modal-backdrop" onClick={ onClose } />
			<div className="bm-rise relative z-10 flex h-[min(900px,calc(100dvh-1.5rem))] w-full max-w-[1600px] flex-col overflow-hidden rounded-xl border border-outline-variant/30 bg-surface-lowest shadow-card">
				<header className="flex shrink-0 items-start justify-between gap-4 border-b border-outline-variant/20 px-5 py-4">
					<div className="min-w-0">
						<p className="mb-1 text-caps-micro font-bold uppercase tracking-widest text-outline">
							{ __( 'Preview', 'joc-blockora-morph' ) }
						</p>
						<h2
							id="bm-block-preview-title"
							className="m-0 truncate text-headline-lg text-on-surface"
						>
							{ block.name }
						</h2>
						<p className="mt-1 mb-0 text-body-md text-on-surface-variant">
							{ block.systemName }
							{ block.variant ? ` · ${ block.variant }` : '' }
						</p>
					</div>
					<button
						type="button"
						className="bm-btn bm-btn--ghost bm-btn--sm shrink-0"
						onClick={ onClose }
					>
						{ __( 'Close', 'joc-blockora-morph' ) }
					</button>
				</header>
				<PreviewFrame
					html={ preview }
					className="min-h-0 flex-1"
					viewport={ viewport }
					onViewportChange={ setViewport }
				/>
				<footer className="flex shrink-0 flex-wrap items-center justify-end gap-2 border-t border-outline-variant/20 bg-surface-low/60 px-5 py-3">
					<button
						type="button"
						className="bm-btn bm-btn--ghost bm-btn--sm"
						onClick={ onClose }
					>
						{ __( 'Close', 'joc-blockora-morph' ) }
					</button>
					<button
						type="button"
						className="bm-btn bm-btn--primary bm-btn--sm"
						onClick={ onCustomize }
					>
						{ __( 'Customize', 'joc-blockora-morph' ) }
					</button>
				</footer>
			</div>
		</div>,
		portalTarget
	);
}

/**
 * Dedicated list: Gutenberg (editor) blocks.
 */
export default function GutenbergBlocks( {
	onNavigate,
	embedded = false,
	sidebarCategory = null,
	onLibraryChange,
} ) {
	const [ blocks, setBlocks ] = useState( [] );
	const [ loading, setLoading ] = useState( true );
	const [ query, setQuery ] = useState( '' );
	const [ category, setCategory ] = useState( 'all' );
	const [ postType, setPostType ] = useState( 'all' );
	const [ postTypeLabels, setPostTypeLabels ] = useState( [] );
	const [ updateTarget, setUpdateTarget ] = useState( null );
	const [ previewBlock, setPreviewBlock ] = useState( null );
	const [ confirmAction, setConfirmAction ] = useState( null );
	const [ confirmBusy, setConfirmBusy ] = useState( false );
	const [ cloneTarget, setCloneTarget ] = useState( null );
	const [ cloning, setCloning ] = useState( false );
	const [ selectedIds, setSelectedIds ] = useState( [] );
	const [ helpOpen, setHelpOpen ] = useState( false );
	const [ browseMode, setBrowseMode ] = useState( loadBrowseMode );
	const [ activeCategory, setActiveCategoryState ] = useState(
		() => ( loadBrowseMode() === 'categories' ? loadActiveCategory() : null )
	);

	const sidebarFilter =
		embedded &&
		sidebarCategory &&
		sidebarCategory !== 'all'
			? String( sidebarCategory )
			: null;

	const setActiveCategory = ( slug ) => {
		const next = slug ? String( slug ).trim() || null : null;
		setActiveCategoryState( next );
		persistActiveCategory( next );
	};

	const setBrowseModePersist = ( mode ) => {
		const next = mode === 'list' ? 'list' : 'categories';
		setBrowseMode( next );
		try {
			window.localStorage?.setItem( BROWSE_MODE_KEY, next );
		} catch ( e ) {
			// ignore
		}
		if ( next === 'list' ) {
			setActiveCategory( null );
		}
	};

	const load = () =>
		api.get( '/user-blocks' ).then( ( res ) => {
			const items = res.items || [];
			setBlocks( items );
			const available = new Set( items.map( ( b ) => b.id ) );
			setSelectedIds( ( prev ) =>
				prev.filter( ( id ) => available.has( id ) )
			);
			return items;
		} );

	const reloadLibrary = async () => {
		await load();
		if ( typeof onLibraryChange === 'function' ) {
			onLibraryChange();
		}
	};

	useEffect( () => {
		load().finally( () => setLoading( false ) );
		api.get( '/post-types' )
			.then( ( res ) => setPostTypeLabels( res.items || [] ) )
			.catch( () =>
				setPostTypeLabels( [
					{ slug: 'post', label: __( 'Posts', 'joc-blockora-morph' ) },
					{ slug: 'page', label: __( 'Pages', 'joc-blockora-morph' ) },
				] )
			);
	}, [] );

	useEffect( () => {
		if ( loading || ! activeCategory || browseMode !== 'categories' ) {
			return;
		}
		const exists = ( blocks || [] ).some( ( b ) =>
			blockMatchesCategory( b, activeCategory )
		);
		if ( ! exists ) {
			setActiveCategory( null );
		}
	}, [ loading, blocks, activeCategory, browseMode ] );

	const categoryOptions = useMemo(
		() => categoryOptionsFromItems( blocks ),
		[ blocks ]
	);

	const categoryCards = useMemo( () => {
		const map = new Map();
		( blocks || [] ).forEach( ( block ) => {
			const slugs = blockCategorySlugs( block );
			slugs.forEach( ( slug ) => {
				const name =
					slug === String( block.categorySlug || '' ).trim()
						? String( block.categoryName || '' ).trim() ||
						  ( slug === 'uncategorized'
								? __( 'Uncategorized', 'joc-blockora-morph' )
								: slug )
						: slug === 'uncategorized'
							? __( 'Uncategorized', 'joc-blockora-morph' )
							: slug;
				if ( ! map.has( slug ) ) {
					map.set( slug, {
						slug,
						name,
						count: 0,
						updates: 0,
						enabled: 0,
						icon: blockDashicon( slug ),
					} );
				}
				const row = map.get( slug );
				row.count += 1;
				if ( block.updateAvailable ) {
					row.updates += 1;
				}
				if ( block.isEnabled ) {
					row.enabled += 1;
				}
			} );
		} );
		return Array.from( map.values() ).sort( ( a, b ) =>
			a.name.localeCompare( b.name, undefined, { sensitivity: 'base' } )
		);
	}, [ blocks ] );

	const postTypeOptions = useMemo(
		() => postTypeOptionsFromItems( blocks, postTypeLabels ),
		[ blocks, postTypeLabels ]
	);

	const effectiveCategory = sidebarFilter
		? sidebarFilter
		: browseMode === 'categories' && activeCategory
		? activeCategory
		: category;

	const showCategoryBrowse =
		! sidebarFilter &&
		browseMode === 'categories' &&
		! activeCategory &&
		blocks.length > 0;

	const showBlockList =
		! loading &&
		blocks.length > 0 &&
		( Boolean( sidebarFilter ) ||
			browseMode === 'list' ||
			Boolean( activeCategory ) );

	const activeCategoryMeta = useMemo( () => {
		if ( ! activeCategory ) {
			return null;
		}
		return (
			categoryCards.find( ( c ) => c.slug === activeCategory ) || {
				slug: activeCategory,
				name: activeCategory,
				count: 0,
				updates: 0,
				enabled: 0,
				icon: blockDashicon( activeCategory ),
			}
		);
	}, [ activeCategory, categoryCards ] );

	const filteredBlocks = useMemo(
		() =>
			filterBlocksList( blocks, {
				query,
				category: effectiveCategory,
				postType,
				getHaystack: ( b ) =>
					`${ b.name || '' } ${ b.systemName || '' } ${ b.categoryName || '' } ${ b.variant || '' } ${ ( b.postTypes || [] ).join( ' ' ) }`,
			} ),
		[ blocks, query, effectiveCategory, postType ]
	);

	const selectedInView = useMemo( () => {
		const viewIds = new Set( filteredBlocks.map( ( b ) => b.id ) );
		return selectedIds.filter( ( id ) => viewIds.has( id ) );
	}, [ selectedIds, filteredBlocks ] );

	const selectedUpdatableIds = useMemo( () => {
		const updatable = new Set(
			blocks.filter( ( b ) => b.updateAvailable ).map( ( b ) => b.id )
		);
		return selectedIds.filter( ( id ) => updatable.has( id ) );
	}, [ selectedIds, blocks ] );

	const allFilteredSelected =
		filteredBlocks.length > 0 &&
		selectedInView.length === filteredBlocks.length;

	const toggleSelected = ( id ) => {
		setSelectedIds( ( prev ) =>
			prev.includes( id )
				? prev.filter( ( x ) => x !== id )
				: [ ...prev, id ]
		);
	};

	const toggleSelectAllFiltered = () => {
		if ( allFilteredSelected ) {
			const drop = new Set( filteredBlocks.map( ( b ) => b.id ) );
			setSelectedIds( ( prev ) =>
				prev.filter( ( id ) => ! drop.has( id ) )
			);
			return;
		}
		setSelectedIds( ( prev ) => {
			const next = new Set( prev );
			filteredBlocks.forEach( ( b ) => next.add( b.id ) );
			return Array.from( next );
		} );
	};

	const requestToggle = ( block ) => {
		if ( block.isEnabled ) {
			setConfirmAction( { type: 'hide', block } );
			return;
		}
		toggle( block.id );
	};

	const toggle = async ( id ) => {
		setConfirmBusy( true );
		try {
			await api.post( `/user-blocks/${ id }/toggle`, {} );
			await reloadLibrary();
			await refreshButtonLibrary();
			setConfirmAction( null );
		} catch ( e ) {
			window.alert(
				e?.message || __( 'Could not update block visibility.', 'joc-blockora-morph' )
			);
		}
		setConfirmBusy( false );
	};

	const confirmClone = async ( name ) => {
		if ( ! cloneTarget?.id || cloning ) {
			return;
		}
		setCloning( true );
		try {
			const created = await api.post(
				`/user-blocks/${ cloneTarget.id }/duplicate`,
				{ name }
			);
			setCloneTarget( null );
			await reloadLibrary();
			await refreshButtonLibrary();
			// Stay on the list so the new block is visible; open editor on demand.
			if ( created?.id ) {
				window.requestAnimationFrame( () => {
					const el = document.querySelector(
						`[data-bm-block-id="${ created.id }"]`
					);
					el?.scrollIntoView?.( { behavior: 'smooth', block: 'nearest' } );
				} );
			}
		} catch ( e ) {
			window.alert(
				e?.message || __( 'Could not duplicate block.', 'joc-blockora-morph' )
			);
		}
		setCloning( false );
	};

	const remove = async ( id ) => {
		setConfirmBusy( true );
		try {
			await api.del( `/user-blocks/${ id }` );
			await reloadLibrary();
			await refreshButtonLibrary();
			setConfirmAction( null );
		} catch ( e ) {
			window.alert(
				e?.message || __( 'Could not delete block.', 'joc-blockora-morph' )
			);
		}
		setConfirmBusy( false );
	};

	const closeConfirm = () => {
		if ( ! confirmBusy ) {
			setConfirmAction( null );
		}
	};

	const runConfirm = () => {
		if ( ! confirmAction ) {
			return;
		}
		if ( confirmAction.type === 'hide' && confirmAction.block ) {
			toggle( confirmAction.block.id );
			return;
		}
		if ( confirmAction.type === 'delete' && confirmAction.block ) {
			remove( confirmAction.block.id );
			return;
		}
		if (
			confirmAction.type === 'bulk-update' &&
			Array.isArray( confirmAction.ids )
		) {
			runBulkUpdate( confirmAction.ids );
			return;
		}
		if (
			confirmAction.type === 'bulk-delete' &&
			Array.isArray( confirmAction.ids )
		) {
			runBulkDelete( confirmAction.ids );
		}
	};

	const runBulkUpdate = async ( ids ) => {
		setConfirmBusy( true );
		try {
			const res = await api.post( '/user-blocks/sync-templates', {
				ids,
				all: false,
			} );
			await reloadLibrary();
			setSelectedIds( [] );
			setConfirmAction( null );
			const failed = res?.failed?.length || 0;
			const count = res?.count || 0;
			if ( failed > 0 ) {
				window.alert(
					sprintf(
						/* translators: 1: updated count, 2: failed count */
						__(
							'Updated %1$d block(s); %2$d failed.',
							'joc-blockora-morph'
						),
						count,
						failed
					)
				);
			}
		} catch ( e ) {
			window.alert(
				e?.message ||
					__( 'Could not apply library updates.', 'joc-blockora-morph' )
			);
		}
		setConfirmBusy( false );
	};

	const runBulkDelete = async ( ids ) => {
		setConfirmBusy( true );
		try {
			const results = await Promise.allSettled(
				ids.map( ( id ) => api.del( `/user-blocks/${ id }` ) )
			);
			const failed = results.filter( ( r ) => r.status === 'rejected' )
				.length;
			await reloadLibrary();
			await refreshButtonLibrary();
			setSelectedIds( [] );
			setConfirmAction( null );
			if ( failed > 0 ) {
				window.alert(
					sprintf(
						/* translators: 1: deleted count, 2: failed count */
						__(
							'Deleted %1$d block(s); %2$d failed.',
							'joc-blockora-morph'
						),
						ids.length - failed,
						failed
					)
				);
			}
		} catch ( e ) {
			window.alert(
				e?.message || __( 'Could not delete blocks.', 'joc-blockora-morph' )
			);
		}
		setConfirmBusy( false );
	};

	return (
		<div
			className={
				embedded
					? 'bm-rise w-full font-sans'
					: 'bm-rise mx-auto w-full max-w-canvas px-gutter py-stack-lg font-sans'
			}
		>
			<header className="mb-stack-lg flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
				<div className="space-y-2">
					{ ! embedded ? (
						<button
							type="button"
							className="bm-btn bm-btn--ghost bm-btn--sm"
							onClick={ () => onNavigate( 'my-blocks' ) }
						>
							← { __( 'Dashboard', 'joc-blockora-morph' ) }
						</button>
					) : (
						<p className="m-0 text-caps-micro font-bold uppercase tracking-widest text-outline">
							{ __( 'My Library', 'joc-blockora-morph' ) }
						</p>
					) }
					{ ! embedded ? (
						<p className="m-0 text-caps-micro font-bold uppercase tracking-widest text-outline">
							{ __( 'For posts & pages', 'joc-blockora-morph' ) }
						</p>
					) : null }
					<h1 className="m-0 flex items-center gap-2 text-display-sm text-on-background">
						{ ! embedded ? (
							<span
								className="dashicons dashicons-block-default text-primary"
								aria-hidden="true"
							/>
						) : null }
						{ __( 'Gutenberg Blocks', 'joc-blockora-morph' ) }
					</h1>
					<p className="m-0 max-w-2xl text-body-lg text-on-surface-variant">
						{ __(
							'These appear in the WordPress block inserter (+). Customize defaults, then insert them while writing.',
							'joc-blockora-morph'
						) }
					</p>
				</div>
				<div className="bm-gb-header-actions flex flex-wrap items-center gap-2.5 shrink-0 self-start sm:self-center">
					{ ! loading && blocks.length > 0 && ! sidebarFilter ? (
						<div
							className="bm-seg-tabs bm-seg-tabs--2 bm-seg-tabs--toolbar"
							role="tablist"
							aria-label={ __(
								'Browse mode',
								'joc-blockora-morph'
							) }
						>
							<button
								type="button"
								role="tab"
								aria-selected={ browseMode === 'categories' }
								className={
									browseMode === 'categories'
										? 'bm-seg-tab is-active'
										: 'bm-seg-tab'
								}
								onClick={ () =>
									setBrowseModePersist( 'categories' )
								}
							>
								<span
									className="dashicons dashicons-grid-view"
									aria-hidden="true"
								/>
								{ __( 'Categories', 'joc-blockora-morph' ) }
							</button>
							<button
								type="button"
								role="tab"
								aria-selected={ browseMode === 'list' }
								className={
									browseMode === 'list'
										? 'bm-seg-tab is-active'
										: 'bm-seg-tab'
								}
								onClick={ () => setBrowseModePersist( 'list' ) }
							>
								<span
									className="dashicons dashicons-list-view"
									aria-hidden="true"
								/>
								{ __( 'List', 'joc-blockora-morph' ) }
							</button>
						</div>
					) : null }
					<button
						type="button"
						className="bm-btn bm-btn--primary shrink-0"
						onClick={ () => onNavigate( 'library' ) }
					>
						{ __( 'Browse JBM Library', 'joc-blockora-morph' ) }
					</button>
				</div>
			</header>

			{ showBlockList ? (
				<>
					{ browseMode === 'categories' && activeCategoryMeta ? (
						<div className="bm-cat-open mb-stack-md">
							<button
								type="button"
								className="bm-btn bm-btn--ghost bm-btn--sm bm-cat-open__back"
								onClick={ () => setActiveCategory( null ) }
							>
								←{ ' ' }
								{ __( 'Back to categories', 'joc-blockora-morph' ) }
							</button>
							<div
								className="bm-cat-open__panel"
								aria-label={ sprintf(
									/* translators: %s: category name */
									__(
										'Open folder: %s',
										'joc-blockora-morph'
									),
									activeCategoryMeta.name
								) }
							>
								<span
									className="bm-folder bm-folder--open"
									aria-hidden="true"
								>
									<span className="bm-folder__graphic">
										<span className="bm-folder__back">
											<span className="bm-folder__tab" />
										</span>
										<span className="bm-folder__body">
											<span className="bm-folder__icon">
												<span
													className={ `dashicons dashicons-${
														activeCategoryMeta.icon ||
														blockDashicon(
															activeCategoryMeta.slug
														)
													}` }
												/>
											</span>
											{ activeCategoryMeta.updates > 0 ? (
												<span className="bm-folder__sticker">
													{ activeCategoryMeta.updates }
												</span>
											) : null }
										</span>
									</span>
								</span>
								<div className="bm-cat-open__copy">
									<p className="bm-cat-open__eyebrow">
										{ __(
											'Category folder',
											'joc-blockora-morph'
										) }
									</p>
									<h2 className="bm-cat-open__title">
										{ activeCategoryMeta.name }
									</h2>
									<p className="bm-cat-open__meta">
										{ sprintf(
											/* translators: %d: block count */
											__(
												'%d block(s) in this folder',
												'joc-blockora-morph'
											),
											activeCategoryMeta.count ||
												filteredBlocks.length
										) }
										{ activeCategoryMeta.enabled > 0
											? ` · ${ sprintf(
													/* translators: %d: enabled count */
													__(
														'%d active',
														'joc-blockora-morph'
													),
													activeCategoryMeta.enabled
											  ) }`
											: '' }
									</p>
								</div>
							</div>
						</div>
					) : null }
					<BlocksListFilters
						query={ query }
						onQueryChange={ setQuery }
						category={
							sidebarFilter ||
							( browseMode === 'categories' && activeCategory
								? 'all'
								: category )
						}
						onCategoryChange={ setCategory }
						categoryOptions={ categoryOptions }
						hideCategoryFilter={
							Boolean( sidebarFilter ) ||
							( browseMode === 'categories' &&
								Boolean( activeCategory ) )
						}
						postType={ postType }
						onPostTypeChange={ setPostType }
						postTypeOptions={ postTypeOptions }
						searchPlaceholder={ __(
							'Search blocks…',
							'joc-blockora-morph'
						) }
						resultCount={ filteredBlocks.length }
						totalCount={
							sidebarFilter
								? blocks.filter( ( b ) =>
										blockMatchesCategory( b, sidebarFilter )
								  ).length
								: browseMode === 'categories' && activeCategory
								? blocks.filter( ( b ) =>
										blockMatchesCategory( b, activeCategory )
								  ).length
								: blocks.length
						}
						onClear={ () => {
							setQuery( '' );
							if ( browseMode === 'list' && ! sidebarFilter ) {
								setCategory( 'all' );
							}
							setPostType( 'all' );
						} }
					/>
				</>
			) : null }

			{ showCategoryBrowse ? (
				<section
					className="bm-cat-grid"
					aria-label={ __( 'Block categories', 'joc-blockora-morph' ) }
				>
					{ categoryCards.map( ( cat ) => (
						<button
							key={ cat.slug }
							type="button"
							className="bm-folder"
							aria-label={
								cat.updates > 0
									? sprintf(
											/* translators: 1: category name, 2: block count, 3: update count */
											__(
												'%1$s folder, %2$d blocks, %3$d updates',
												'joc-blockora-morph'
											),
											cat.name,
											cat.count,
											cat.updates
									  )
									: sprintf(
											/* translators: 1: category name, 2: block count */
											__(
												'%1$s folder, %2$d blocks',
												'joc-blockora-morph'
											),
											cat.name,
											cat.count
									  )
							}
							onClick={ () => {
								setActiveCategory( cat.slug );
								setQuery( '' );
								setPostType( 'all' );
							} }
						>
							<span className="bm-folder__graphic" aria-hidden="true">
								<span className="bm-folder__back">
									<span className="bm-folder__tab" />
								</span>
								<span className="bm-folder__body">
									<span className="bm-folder__icon">
										<span
											className={ `dashicons dashicons-${ cat.icon }` }
										/>
									</span>
									{ cat.updates > 0 ? (
										<span className="bm-folder__sticker">
											{ cat.updates }
										</span>
									) : null }
								</span>
							</span>
							<span className="bm-folder__label">
								<strong className="bm-folder__title">
									{ cat.name }
								</strong>
								<span className="bm-folder__meta">
									{ sprintf(
										/* translators: %d: number of blocks */
										__( '%d block(s)', 'joc-blockora-morph' ),
										cat.count
									) }
									{ cat.enabled > 0
										? ` · ${ sprintf(
												/* translators: %d: enabled count */
												__(
													'%d active',
													'joc-blockora-morph'
												),
												cat.enabled
										  ) }`
										: '' }
								</span>
							</span>
						</button>
					) ) }
				</section>
			) : null }

			{ ! showCategoryBrowse ? (
			<div className="bm-card bm-dash-section__card">
				{ loading && (
					<div className="p-2">
						<LibrarySkeleton variant="list" count={ 5 } />
					</div>
				) }

				{ ! loading && blocks.length === 0 && (
					<div className="bm-empty">
						<span
							className="bm-empty__icon dashicons dashicons-portfolio"
							aria-hidden="true"
						/>
						<h3 className="bm-empty__title">
							{ __( 'Import your first block', 'joc-blockora-morph' ) }
						</h3>
						<p className="bm-empty__text">
							{ __(
								'Open the Library, preview a design, and import it. It will show up here and in the Gutenberg inserter.',
								'joc-blockora-morph'
							) }
						</p>
						<button
							type="button"
							className="bm-btn bm-btn--primary"
							onClick={ () => onNavigate( 'library' ) }
						>
							{ __( 'Open JBM Library', 'joc-blockora-morph' ) }
						</button>
					</div>
				) }

				{ showBlockList && (
					<>
						<div className="bm-help">
							<div className="bm-help__toolbar">
								<button
									type="button"
									className="bm-help__toggle"
									aria-expanded={ helpOpen }
									onClick={ () =>
										setHelpOpen( ( open ) => ! open )
									}
								>
									{ __(
										'How do I find these in Gutenberg?',
										'joc-blockora-morph'
									) }
								</button>
								{ filteredBlocks.length > 0 ? (
									<div className="bm-help__actions">
										<label className="bm-btn bm-btn--soft bm-btn--sm bm-help__select-all">
											<input
												type="checkbox"
												checked={ allFilteredSelected }
												ref={ ( el ) => {
													if ( el ) {
														el.indeterminate =
															selectedInView.length >
																0 &&
															! allFilteredSelected;
													}
												} }
												onChange={
													toggleSelectAllFiltered
												}
											/>
											<span>
												{ allFilteredSelected
													? __(
															'Deselect all',
															'joc-blockora-morph'
													  )
													: __(
															'Select all',
															'joc-blockora-morph'
													  ) }
											</span>
										</label>
										<button
											type="button"
											className="bm-btn bm-btn--primary bm-btn--sm"
											disabled={
												selectedUpdatableIds.length ===
													0 || confirmBusy
											}
											onClick={ () =>
												setConfirmAction( {
													type: 'bulk-update',
													ids: selectedUpdatableIds,
												} )
											}
										>
											{ sprintf(
												/* translators: %d: selected updatable count */
												__(
													'Update (%d)',
													'joc-blockora-morph'
												),
												selectedUpdatableIds.length
											) }
										</button>
										<button
											type="button"
											className="bm-btn bm-btn--danger bm-btn--sm"
											disabled={
												selectedIds.length === 0 ||
												confirmBusy
											}
											onClick={ () =>
												setConfirmAction( {
													type: 'bulk-delete',
													ids: [ ...selectedIds ],
												} )
											}
										>
											{ sprintf(
												/* translators: %d: selected count */
												__(
													'Delete (%d)',
													'joc-blockora-morph'
												),
												selectedIds.length
											) }
										</button>
									</div>
								) : null }
							</div>
							{ helpOpen ? (
								<ol className="bm-help__list">
									<li>
										{ __(
											'Edit a post or page in the block editor.',
											'joc-blockora-morph'
										) }
									</li>
									<li>
										{ __(
											'Click + to open the inserter, then Browse all.',
											'joc-blockora-morph'
										) }
									</li>
									<li>
										{ __(
											'Open the JOC Blockora Morph category — or search the block name below.',
											'joc-blockora-morph'
										) }
									</li>
								</ol>
							) : null }
						</div>

						{ filteredBlocks.length === 0 ? (
							<p className="bm-list-filters__empty">
								{ __(
									'No blocks match your search or category.',
									'joc-blockora-morph'
								) }
							</p>
						) : (
						<>
						<div className="bm-gb-list bm-gb-list--selectable">
							{ filteredBlocks.map( ( block ) => (
								<article
									key={ block.id }
									data-bm-block-id={ block.id }
									className={
										block.updateAvailable
											? 'bm-gb-card has-update'
											: 'bm-gb-card'
									}
								>
									<label className="bm-gb-card__check">
										<input
											type="checkbox"
											checked={ selectedIds.includes(
												block.id
											) }
											onChange={ () =>
												toggleSelected( block.id )
											}
											aria-label={ sprintf(
												/* translators: %s: block name */
												__(
													'Select %s',
													'joc-blockora-morph'
												),
												block.name
											) }
										/>
									</label>
									<button
										type="button"
										className="bm-gb-card__thumb"
										onClick={ () => setPreviewBlock( block ) }
										aria-label={ __(
											'Preview saved block',
											'joc-blockora-morph'
										) }
									>
										<LibraryCardThumb block={ block } />
									</button>

									<div className="bm-gb-card__content">
										<div className="bm-gb-card__main">
											<div className="bm-gb-card__body min-w-0">
												<div className="bm-gb-card__title-row">
													<strong className="bm-gb-card__title">
														{ block.name }
													</strong>
													<span className="bm-gb-card__badges">
														{ block.isEnabled ? (
															<span className="bm-badge bm-badge--active">
																{ __( 'Active', 'joc-blockora-morph' ) }
															</span>
														) : (
															<span className="bm-badge bm-badge--disabled">
																{ __( 'Hidden', 'joc-blockora-morph' ) }
															</span>
														) }
														{ block.updateAvailable ? (
															<span className="bm-badge bm-badge--update">
																{ __(
																	'Update available',
																	'joc-blockora-morph'
																) }
															</span>
														) : null }
													</span>
												</div>
												<p className="bm-gb-card__meta">
													{ block.categoryName
														? `${ block.categoryName } · `
														: '' }
													{ block.systemName }
													{ block.variant
														? ` · ${ block.variant }`
														: '' }
												</p>
											</div>
											{ block.updateAvailable ? (
												<button
													type="button"
													className="bm-btn bm-btn--primary bm-btn--sm bm-gb-card__update"
													onClick={ () =>
														setUpdateTarget( {
															id: block.id,
															name: block.name,
														} )
													}
												>
													{ __( 'Review update', 'joc-blockora-morph' ) }
												</button>
											) : null }
										</div>

										<div className="bm-gb-card__actions">
											<button
												type="button"
												className="bm-btn bm-btn--ghost bm-btn--sm"
												onClick={ () => setPreviewBlock( block ) }
											>
												<span
													className="dashicons dashicons-visibility"
													aria-hidden="true"
												/>
												{ __( 'Preview', 'joc-blockora-morph' ) }
											</button>
											<button
												type="button"
												className="bm-gb-card__icon-btn"
												onClick={ () =>
													onNavigate( `editor/${ block.id }`, {
														userBlock: block,
													} )
												}
												aria-label={ __(
													'Edit block',
													'joc-blockora-morph'
												) }
												title={ __( 'Edit', 'joc-blockora-morph' ) }
											>
												<span
													className="dashicons dashicons-edit"
													aria-hidden="true"
												/>
											</button>
											<button
												type="button"
												className="bm-gb-card__icon-btn"
												onClick={ () => setCloneTarget( block ) }
												disabled={ cloning }
												aria-label={ __(
													'Duplicate block',
													'joc-blockora-morph'
												) }
												title={ __( 'Duplicate', 'joc-blockora-morph' ) }
											>
												<span
													className="dashicons dashicons-admin-page"
													aria-hidden="true"
												/>
											</button>
											<button
												type="button"
												className="bm-gb-card__icon-btn"
												onClick={ () => requestToggle( block ) }
												aria-label={
													block.isEnabled
														? __(
																'Hide from inserter',
																'joc-blockora-morph'
														  )
														: __(
																'Show in inserter',
																'joc-blockora-morph'
														  )
												}
												title={
													block.isEnabled
														? __(
																'Hide from inserter',
																'joc-blockora-morph'
														  )
														: __(
																'Show in inserter',
																'joc-blockora-morph'
														  )
												}
											>
												<span
													className={
														block.isEnabled
															? 'dashicons dashicons-visibility'
															: 'dashicons dashicons-hidden'
													}
													aria-hidden="true"
												/>
											</button>
											<button
												type="button"
												className="bm-gb-card__icon-btn is-danger"
												onClick={ () =>
													setConfirmAction( {
														type: 'delete',
														block,
													} )
												}
												aria-label={ __(
													'Delete block',
													'joc-blockora-morph'
												) }
												title={ __( 'Delete', 'joc-blockora-morph' ) }
											>
												<span
													className="dashicons dashicons-trash"
													aria-hidden="true"
												/>
											</button>
										</div>
									</div>
								</article>
							) ) }
						</div>
						</>
						) }
					</>
				) }
			</div>
			) : null }

			{ updateTarget ? (
				<TemplateUpdateModal
					blockId={ updateTarget.id }
					blockName={ updateTarget.name }
					onClose={ () => setUpdateTarget( null ) }
					onApplied={ () => {
						setSelectedIds( [] );
						load();
					} }
				/>
			) : null }

			{ previewBlock ? (
				<BlockPreviewModal
					block={ previewBlock }
					onClose={ () => setPreviewBlock( null ) }
					onCustomize={ () => {
						const id = previewBlock.id;
						setPreviewBlock( null );
						onNavigate( `editor/${ id }`, { userBlock: previewBlock } );
					} }
				/>
			) : null }

			{ cloneTarget ? (
				<CloneBlockModal
					block={ cloneTarget }
					busy={ cloning }
					onClose={ () => {
						if ( ! cloning ) {
							setCloneTarget( null );
						}
					} }
					onConfirm={ confirmClone }
				/>
			) : null }

			<ConfirmModal
				open={ confirmAction?.type === 'bulk-update' }
				title={ __( 'Update selected blocks?', 'joc-blockora-morph' ) }
				description={ __(
					'Library markup and schemas will be applied. Your field values and style overrides are kept. This skips the per-block review screen.',
					'joc-blockora-morph'
				) }
				icon="update"
				tone="warning"
				busy={ confirmBusy }
				confirmLabel={ sprintf(
					/* translators: %d: number of blocks */
					__( 'Update selected (%d)', 'joc-blockora-morph' ),
					confirmAction?.ids?.length || 0
				) }
				onConfirm={ runConfirm }
				onCancel={ closeConfirm }
			>
				{ confirmAction?.type === 'bulk-update' ? (
					<BulkConfirmItemsList
						label={ __( 'Blocks', 'joc-blockora-morph' ) }
						items={ ( confirmAction.ids || [] ).map( ( id ) => {
							const b = blocks.find( ( item ) => item.id === id );
							const metaParts = [
								b?.categoryName,
								b?.systemName,
								b?.variant,
							].filter( Boolean );
							return {
								id,
								title: b?.name || `#${ id }`,
								meta: metaParts.join( ' · ' ),
							};
						} ) }
					/>
				) : null }
			</ConfirmModal>

			<ConfirmModal
				open={ confirmAction?.type === 'bulk-delete' }
				title={ __( 'Delete selected blocks?', 'joc-blockora-morph' ) }
				description={ __(
					'This permanently removes the selected Gutenberg blocks. Shortcodes based on them will stop working.',
					'joc-blockora-morph'
				) }
				icon="trash"
				tone="danger"
				busy={ confirmBusy }
				confirmLabel={ sprintf(
					/* translators: %d: number of blocks */
					__( 'Delete selected (%d)', 'joc-blockora-morph' ),
					confirmAction?.ids?.length || 0
				) }
				onConfirm={ runConfirm }
				onCancel={ closeConfirm }
			>
				{ confirmAction?.type === 'bulk-delete' ? (
					<BulkConfirmItemsList
						label={ __( 'Blocks', 'joc-blockora-morph' ) }
						items={ ( confirmAction.ids || [] ).map( ( id ) => {
							const b = blocks.find( ( item ) => item.id === id );
							const metaParts = [
								b?.categoryName,
								b?.systemName,
								b?.variant,
							].filter( Boolean );
							return {
								id,
								title: b?.name || `#${ id }`,
								meta: metaParts.join( ' · ' ),
							};
						} ) }
					/>
				) : null }
			</ConfirmModal>

			<ConfirmModal
				open={ confirmAction?.type === 'hide' }
				title={ __( 'Hide from inserter?', 'joc-blockora-morph' ) }
				description={ __(
					'This block will stay in My Blocks, but writers won’t see it in the Gutenberg + menu until you show it again.',
					'joc-blockora-morph'
				) }
				icon="hidden"
				tone="warning"
				busy={ confirmBusy }
				confirmLabel={ __( 'Hide block', 'joc-blockora-morph' ) }
				onConfirm={ runConfirm }
				onCancel={ closeConfirm }
			>
				{ confirmAction?.type === 'hide' && confirmAction.block ? (
					<div className="bm-confirm-modal__meta">
						<div className="bm-confirm-modal__meta-row">
							<span className="bm-confirm-modal__meta-label">
								{ __( 'Block', 'joc-blockora-morph' ) }
							</span>
							<span className="bm-confirm-modal__meta-value">
								{ confirmAction.block.name }
							</span>
						</div>
						{ confirmAction.block.systemName ? (
							<div className="bm-confirm-modal__meta-row">
								<span className="bm-confirm-modal__meta-label">
									{ __( 'Template', 'joc-blockora-morph' ) }
								</span>
								<span className="bm-confirm-modal__meta-value">
									{ confirmAction.block.systemName }
									{ confirmAction.block.variant
										? ` · ${ confirmAction.block.variant }`
										: '' }
								</span>
							</div>
						) : null }
					</div>
				) : null }
			</ConfirmModal>

			<ConfirmModal
				open={ confirmAction?.type === 'delete' }
				title={ __( 'Delete this block?', 'joc-blockora-morph' ) }
				description={ __(
					'This permanently removes the Gutenberg block. Shortcodes based on it will stop working.',
					'joc-blockora-morph'
				) }
				icon="trash"
				tone="danger"
				busy={ confirmBusy }
				confirmLabel={ __( 'Delete block', 'joc-blockora-morph' ) }
				onConfirm={ runConfirm }
				onCancel={ closeConfirm }
			>
				{ confirmAction?.type === 'delete' && confirmAction.block ? (
					<div className="bm-confirm-modal__meta">
						<div className="bm-confirm-modal__meta-row">
							<span className="bm-confirm-modal__meta-label">
								{ __( 'Block', 'joc-blockora-morph' ) }
							</span>
							<span className="bm-confirm-modal__meta-value">
								{ confirmAction.block.name }
							</span>
						</div>
						{ confirmAction.block.systemName ? (
							<div className="bm-confirm-modal__meta-row">
								<span className="bm-confirm-modal__meta-label">
									{ __( 'Template', 'joc-blockora-morph' ) }
								</span>
								<span className="bm-confirm-modal__meta-value">
									{ confirmAction.block.systemName }
									{ confirmAction.block.variant
										? ` · ${ confirmAction.block.variant }`
										: '' }
								</span>
							</div>
						) : null }
					</div>
				) : null }
			</ConfirmModal>
		</div>
	);
}
