/**
 * Repeater field (e.g. buttons with links — styles live in the Styles panel).
 */
import { Button, Popover, TextControl, ToggleControl, SelectControl } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { normalizeLink, normalizeMedia } from '../../interpolate';
import { createDefaultHeroButton, duplicateHeroButtonAt } from '../../shared/buttonStyleBaseline';
import {
	createDefaultHeroSlide,
	MIN_HERO_SLIDES,
} from '../../shared/sliderHelpers';
import { SLIDE_LAYOUT_OPTIONS, slideLayoutLabel } from '../../shared/sliderElements';
import { sampleLogoItem } from '../../shared/sampleLogos';
import {
	applySharedCollectionIcon,
	createDefaultCollectionItem,
	collectionItemNoun,
	collectionMinItems,
	collectionRemoveConfirmCopy,
	collectionSharesIcon,
} from '../../shared/collectionRepeater';
import { reorderCollectionArray } from '../CardsList';
import ConfirmModal from '../ConfirmModal';
import BmMediaUpload from './BmMediaUpload';
import ButtonIconPicker from './ButtonIconPicker';
import LinkEditorForm from './LinkEditorForm';
import LinkField from './LinkField';
import MediaField from './MediaField';
import TagField from './TagField';
import SearchableSelect from '../SearchableSelect';
import TextWysiwygField, { isTextWysiwygType } from './TextWysiwygField';
import {
	endPreviewContentEditForLinkEditor,
	patchPreviewButtonLink,
} from '../../shared/previewContentEdit';

function isInteractiveTarget( target ) {
	if ( ! ( target instanceof Element ) ) {
		return false;
	}
	return Boolean(
		target.closest(
			'button, a, input, select, textarea, [role="button"], .components-button'
		)
	);
}
function selectOptions( field ) {
	const raw = Array.isArray( field?.options ) ? field.options : [];
	return raw
		.map( ( opt ) => {
			if ( typeof opt === 'string' ) {
				return { label: opt, value: opt };
			}
			if ( opt && typeof opt === 'object' ) {
				const value = String( opt.value ?? opt.id ?? '' );
				const label = String( opt.label ?? opt.name ?? value );
				return value ? { label, value } : null;
			}
			return null;
		} )
		.filter( Boolean );
}

function ItemField( { field, value, onChange, hideLabel, allowManageButtonItems = true } ) {
	if ( field.type === 'link' ) {
		return (
			<LinkField
				label={ hideLabel ? '' : field.label }
				value={ value }
				onChange={ onChange }
			/>
		);
	}
	if ( field.type === 'media' ) {
		return (
			<MediaField
				label={ hideLabel ? '' : field.label }
				value={ value }
				onChange={ onChange }
				field={ field }
			/>
		);
	}
	if ( field.type === 'tag' ) {
		return (
			<TagField
				label={ hideLabel ? '' : field.label }
				value={ value }
				onChange={ onChange }
				field={ field }
			/>
		);
	}
	if ( field.type === 'elementsOrder' ) {
		// Element reorder is only available in the admin Block Editor.
		return null;
	}
	if ( field.type === 'url' ) {
		return (
			<TextControl
				label={ hideLabel ? undefined : field.label }
				type="url"
				value={ value || '' }
				onChange={ onChange }
				__nextHasNoMarginBottom
				__next40pxDefaultSize
			/>
		);
	}
	if ( isTextWysiwygType( field.type || 'text' ) ) {
		return (
			<TextWysiwygField
				label={ hideLabel ? '' : field.label }
				value={ value || '' }
				onChange={ onChange }
				help={ field.help || '' }
				hideLabel={ hideLabel }
				field={ field }
				allowedCommand={
					field.allowedCommand ?? field.allowedCommands ?? undefined
				}
				disableAdvanceEditor={
					field.disableAdvanceEditor ??
					field.disableAdvancedEditor ??
					undefined
				}
			/>
		);
	}
	if ( field.type === 'dashicon' || field.type === 'icon' ) {
		return (
			<div className="bm-field">
				{ ! hideLabel && field.label ? (
					<span className="bm-field-label">{ field.label }</span>
				) : null }
				<ButtonIconPicker
					value={ value || '' }
					onChange={ onChange }
				/>
			</div>
		);
	}
	if ( field.type === 'toggle' || field.type === 'boolean' ) {
		return (
			<ToggleControl
				label={ hideLabel ? undefined : field.label }
				checked={
					value === true ||
					value === 1 ||
					value === '1' ||
					value === 'true'
				}
				onChange={ ( next ) => onChange( Boolean( next ) ) }
				__nextHasNoMarginBottom
			/>
		);
	}
	if ( field.type === 'number' ) {
		return (
			<TextControl
				label={ hideLabel ? undefined : field.label }
				type="number"
				value={ value === '' || value == null ? '' : String( value ) }
				onChange={ ( next ) => {
					if ( next === '' || next == null ) {
						onChange( '' );
						return;
					}
					const n = Number( next );
					onChange( Number.isFinite( n ) ? n : next );
				} }
				min={ field.min != null ? Number( field.min ) : undefined }
				max={ field.max != null ? Number( field.max ) : undefined }
				step={ field.step != null ? Number( field.step ) : undefined }
				__nextHasNoMarginBottom
				__next40pxDefaultSize
			/>
		);
	}
	if ( field.type === 'select' ) {
		const optionsList = selectOptions( field );
		const fallback = String(
			field.default ?? optionsList[ 0 ]?.value ?? ''
		);
		return (
			<SearchableSelect
				label={ hideLabel ? undefined : field.label }
				value={ value != null && value !== '' ? String( value ) : fallback }
				options={ optionsList }
				onChange={ ( next ) => onChange( next || fallback ) }
			/>
		);
	}
	if ( field.type === 'repeater' ) {
		return (
			<RepeaterField
				label={ hideLabel ? '' : field.label }
				value={ Array.isArray( value ) ? value : [] }
				onChange={ onChange }
				field={ field }
				allowManageButtonItems={ allowManageButtonItems }
			/>
		);
	}
	return (
		<TextWysiwygField
			label={ hideLabel ? '' : field.label }
			value={ value || '' }
			onChange={ onChange }
			hideLabel={ hideLabel }
			field={ field }
			allowedCommand={
				field.allowedCommand ?? field.allowedCommands ?? undefined
			}
			disableAdvanceEditor={
				field.disableAdvanceEditor ??
				field.disableAdvancedEditor ??
				undefined
			}
		/>
	);
}

function defaultItem( fields = [], itemType = '', index = 0 ) {
	if ( itemType === 'logo' ) {
		return sampleLogoItem( index );
	}
	if ( itemType === 'slide' ) {
		return createDefaultHeroSlide();
	}
	if ( itemType === 'tab' ) {
		const seeded = createDefaultCollectionItem(
			{ fields, itemType },
			index
		);
		if ( ! seeded.tabTitle ) {
			seeded.tabTitle = __( 'New tab', 'joc-blockora-morph' );
		}
		if ( ! seeded.tabContent ) {
			seeded.tabContent = __(
				'Add your tab content here.',
				'joc-blockora-morph'
			);
		}
		return seeded;
	}
	if ( itemType === 'card' ) {
		const seeded = createDefaultCollectionItem(
			{ fields, itemType },
			index
		);
		if ( ! seeded.title ) {
			seeded.title = __( 'New card', 'joc-blockora-morph' );
		}
		if ( ! seeded.description ) {
			seeded.description = __(
				'Add your card description here.',
				'joc-blockora-morph'
			);
		}
		return seeded;
	}
	const item = {};
	( fields || [] ).forEach( ( f ) => {
		if ( ! f?.key ) {
			return;
		}
		if ( f.type === 'link' ) {
			item[ f.key ] = normalizeLink(
				f.default && typeof f.default === 'object' ? f.default : {}
			);
		} else if ( f.type === 'media' ) {
			item[ f.key ] = normalizeMedia(
				f.default && typeof f.default === 'object'
					? f.default
					: { id: 0, url: '', alt: '', mime: '', type: 'image' }
			);
		} else if ( f.type === 'toggle' || f.type === 'boolean' ) {
			item[ f.key ] = Boolean( f.default );
		} else if ( f.type === 'number' ) {
			item[ f.key ] =
				f.default != null && f.default !== ''
					? Number( f.default )
					: '';
		} else if ( f.type === 'select' ) {
			const opts = selectOptions( f );
			item[ f.key ] = String( f.default ?? opts[ 0 ]?.value ?? '' );
		} else if ( f.type === 'repeater' ) {
			item[ f.key ] = Array.isArray( f.default ) ? f.default : [];
		} else if ( f.type === 'icon' || f.type === 'dashicon' ) {
			item[ f.key ] =
				f.default != null ? String( f.default ) : 'dashicons:star-filled';
		} else {
			item[ f.key ] = f.default != null ? String( f.default ) : '';
		}
	} );
	if ( itemType === 'button' ) {
		const seeded = createDefaultHeroButton( item );
		Object.assign( item, seeded );
		if ( item.link && typeof item.link === 'object' ) {
			item.link = normalizeLink( item.link );
		}
	}
	if ( itemType === 'testimonial' ) {
		return applySharedCollectionIcon( item, { fields, itemType }, [] );
	}
	return item;
}

function stripPreviewText( value ) {
	let text = String( value || '' );
	// Strip tags first, then decode entities (handles &lt;p&gt;…), then strip again.
	text = text.replace( /<[^>]*>/g, ' ' );
	text = text
		.replace( /&nbsp;/gi, ' ' )
		.replace( /&amp;/gi, '&' )
		.replace( /&lt;/gi, '<' )
		.replace( /&gt;/gi, '>' )
		.replace( /&quot;/gi, '"' )
		.replace( /&#0*39;/g, "'" )
		.replace( /&#x0*27;/gi, "'" )
		.replace( /&#(\d+);/g, ( _, n ) => {
			const code = Number( n );
			return Number.isFinite( code ) ? String.fromCharCode( code ) : '';
		} )
		.replace( /&#x([0-9a-f]+);/gi, ( _, hex ) => {
			const code = parseInt( hex, 16 );
			return Number.isFinite( code ) ? String.fromCharCode( code ) : '';
		} );
	text = text.replace( /<[^>]*>/g, ' ' );
	return text.replace( /\s+/g, ' ' ).trim();
}

/**
 * Hero-slider-style title for collection rows (Card 1: …, Tab 2: …).
 *
 * @param {object} item
 * @param {number} index
 * @param {object} field
 * @param {string} itemType
 * @return {{ heading: string, meta: string }}
 */
function collectionItemLines( item, index, field, itemType ) {
	const typeLabels = {
		slide: __( 'Slide', 'joc-blockora-morph' ),
		card: __( 'Card', 'joc-blockora-morph' ),
		tab: __( 'Tab', 'joc-blockora-morph' ),
		plan: __( 'Plan', 'joc-blockora-morph' ),
		testimonial: __( 'Testimonial', 'joc-blockora-morph' ),
		accordion: __( 'Item', 'joc-blockora-morph' ),
		feature: __( 'Feature', 'joc-blockora-morph' ),
	};
	const base =
		( field.itemLabel && String( field.itemLabel ) ) ||
		typeLabels[ itemType ] ||
		__( 'Item', 'joc-blockora-morph' );
	const title = stripPreviewText(
		item?.title ||
			item?.tabTitle ||
			item?.accordionTitle ||
			item?.header ||
			item?.name ||
			item?.tag ||
			''
	);
	const description = stripPreviewText( item?.description || '' );
	const fallback = stripPreviewText(
		item?.content || item?.text || item?.label || ''
	);
	const slicedTitle = title ? title.slice( 0, 80 ) : '';
	return {
		heading: `${ base } ${ index + 1 }${
			slicedTitle ? `: ${ slicedTitle }` : ''
		}`,
		meta: description
			? description.slice( 0, 120 )
			: ! slicedTitle && fallback
				? fallback.slice( 0, 120 )
				: '',
	};
}

/**
 * Compact logo row: thumbnail | label | Change + delete.
 */
function mediaFromSelection( selected ) {
	return {
		id: selected?.id || 0,
		url: selected?.url || '',
		alt: selected?.alt || selected?.title || '',
		mime: selected?.mime || selected?.mime_type || '',
		type: 'image',
	};
}

function LogoRepeaterItem( { item, onChange, onRemove } ) {
	const media = normalizeMedia( item?.image || {} );

	return (
		<div className="bm-repeater__logo-row">
			<BmMediaUpload
				onSelect={ ( selected ) => {
					onChange( {
						...item,
						image: mediaFromSelection( selected ),
					} );
				} }
				allowedTypes={ [ 'image' ] }
				value={ media.id || undefined }
				title={ __( 'Select logo', 'joc-blockora-morph' ) }
				render={ ( { open } ) => (
					<>
						<button
							type="button"
							className="bm-repeater__logo-thumb"
							onClick={ open }
							aria-label={ __(
								'Replace logo',
								'joc-blockora-morph'
							) }
						>
							{ media.url ? (
								<img src={ media.url } alt="" />
							) : (
								<span
									className="dashicons dashicons-format-image"
									aria-hidden="true"
								/>
							) }
						</button>
						<span className="bm-repeater__logo-meta">
							{ media.url
								? __( 'Logo image', 'joc-blockora-morph' )
								: __( 'Choose image', 'joc-blockora-morph' ) }
						</span>
						<div className="bm-repeater__logo-actions">
							<Button
								size="small"
								variant="tertiary"
								className="bm-repeater__icon-btn"
								onClick={ open }
								aria-label={ __(
									'Change logo',
									'joc-blockora-morph'
								) }
								label={ __( 'Change', 'joc-blockora-morph' ) }
								showTooltip
								icon="update"
							/>
							<Button
								size="small"
								variant="tertiary"
								className="bm-repeater__icon-btn bm-repeater__icon-btn--danger"
								isDestructive
								onClick={ onRemove }
								aria-label={ __(
									'Remove logo',
									'joc-blockora-morph'
								) }
								icon="no-alt"
							/>
						</div>
					</>
				) }
			/>
		</div>
	);
}

/**
 * Button row: link preview + icon picker + delete; footer edit link + icon side.
 */
function ButtonRepeaterItem( {
	item,
	index,
	total,
	onChange,
	onMove,
	onRemove,
	onDuplicate,
	onEditStyles,
	canRemove = true,
	placementChrome = false,
	previewScope = null,
} ) {
	const [ linkOpen, setLinkOpen ] = useState( false );
	const link = normalizeLink( item?.link || {} );
	const icon = String( item?.icon || '' ).trim();
	const title = link.title || __( 'Untitled button', 'joc-blockora-morph' );
	const canEditStyles = typeof onEditStyles === 'function';
	const isHidden =
		item?.hidden === true ||
		item?.hidden === 1 ||
		item?.hidden === '1' ||
		item?.hidden === 'true';

	const applyLink = ( next ) => {
		const n = next || {};
		const normalized = normalizeLink( {
			title: n.title != null ? n.title : link.title,
			url: n.url != null ? n.url : '',
			opensInNewTab: Boolean( n.opensInNewTab ),
		} );
		// Surgical preview update first so remount (if any) matches what the
		// user already sees from draft typing.
		patchPreviewButtonLink( {
			buttonIndex: index,
			title: normalized.title,
			url: normalized.url,
			opensInNewTab: normalized.opensInNewTab,
			cardIndex: previewScope?.cardIndex,
			slideIndex: previewScope?.slideIndex,
		} );
		onChange( {
			...item,
			link: normalized,
		} );
	};

	const draftLinkPreview = ( next ) => {
		const n = next || {};
		patchPreviewButtonLink( {
			buttonIndex: index,
			title: n.title != null ? String( n.title ) : link.title,
			url: n.url != null ? String( n.url ) : link.url,
			opensInNewTab: Boolean( n.opensInNewTab ),
			cardIndex: previewScope?.cardIndex,
			slideIndex: previewScope?.slideIndex,
		} );
	};

	const openLinkEditor = () => {
		endPreviewContentEditForLinkEditor();
		setLinkOpen( ( v ) => ! v );
	};

	const iconPicker = (
		<ButtonIconPicker
			value={ icon }
			onChange={ ( next ) =>
				onChange( { ...item, icon: next || '' } )
			}
		/>
	);

	const designButton = canEditStyles ? (
		<Button
			size="small"
			variant="tertiary"
			className="bm-repeater__icon-btn bm-repeater__icon-btn--styles"
			onClick={ () => onEditStyles( index ) }
			aria-label={ __( 'Edit button styles', 'joc-blockora-morph' ) }
			label={ __( 'Design', 'joc-blockora-morph' ) }
			showTooltip
			icon="art"
		>
			{ placementChrome
				? __( 'Design', 'joc-blockora-morph' )
				: null }
		</Button>
	) : null;

	const moveRemoveActions = (
		<>
			<Button
				size="small"
				variant="tertiary"
				className="bm-repeater__icon-btn"
				disabled={ index === 0 }
				onClick={ () => onMove( -1 ) }
				aria-label={ __( 'Move up', 'joc-blockora-morph' ) }
				icon="arrow-up-alt2"
			/>
			<Button
				size="small"
				variant="tertiary"
				className="bm-repeater__icon-btn"
				disabled={ index === total - 1 }
				onClick={ () => onMove( 1 ) }
				aria-label={ __( 'Move down', 'joc-blockora-morph' ) }
				icon="arrow-down-alt2"
			/>
			{ typeof onDuplicate === 'function' ? (
				<Button
					size="small"
					variant="tertiary"
					className="bm-repeater__icon-btn"
					onClick={ onDuplicate }
					aria-label={ __( 'Duplicate button', 'joc-blockora-morph' ) }
					icon="admin-page"
				/>
			) : null }
			{ typeof onRemove === 'function' ? (
				<Button
					size="small"
					variant="tertiary"
					className="bm-repeater__icon-btn bm-repeater__icon-btn--danger"
					isDestructive
					disabled={ ! canRemove }
					onClick={ onRemove }
					aria-label={ __( 'Remove button', 'joc-blockora-morph' ) }
					label={
						canRemove
							? __( 'Remove button', 'joc-blockora-morph' )
							: __(
									'Keep at least one button. Hide Buttons in Content if you don’t need it.',
									'joc-blockora-morph'
							  )
					}
					showTooltip
					icon="no-alt"
				/>
			) : null }
		</>
	);

	const linkEditor = (
		<>
			<Button
				variant="secondary"
				size="small"
				className="bm-repeater__edit-link"
				icon="edit"
				onClick={ openLinkEditor }
			>
				{ __( 'Edit link', 'joc-blockora-morph' ) }
			</Button>
			{ linkOpen ? (
				<Popover
					onClose={ () => setLinkOpen( false ) }
					placement="bottom-start"
				>
					<div className="bm-link-field__popover">
						<LinkEditorForm
							value={ link }
							onChange={ ( next ) => applyLink( next ) }
							onDraftChange={ draftLinkPreview }
							onDone={ () => setLinkOpen( false ) }
							onRemove={
								link.url
									? () => {
											setLinkOpen( false );
											applyLink( {
												title: '',
												url: '',
												opensInNewTab: false,
											} );
									  }
									: undefined
							}
						/>
					</div>
				</Popover>
			) : null }
		</>
	);

	return (
		<div
			className={
				'bm-repeater__item bm-repeater__item--button' +
				( isHidden ? ' is-button-hidden' : '' )
			}
		>
			<div className="bm-repeater__item-header bm-repeater__item-header--button">
				<span className="bm-repeater__index" aria-hidden="true">
					{ index + 1 }
				</span>
				<div className="bm-repeater__item-summary bm-repeater__item-summary--link">
					<span
						className="bm-repeater__link-icon dashicons dashicons-admin-links"
						aria-hidden="true"
					/>
					<div className="bm-repeater__item-summary-text">
						<strong className="bm-repeater__item-title">{ title }</strong>
						{ link.url ? (
							<span className="bm-repeater__item-url">{ link.url }</span>
						) : (
							<span className="bm-repeater__item-url bm-repeater__item-url--muted">
								{ __( 'No URL set', 'joc-blockora-morph' ) }
							</span>
						) }
					</div>
				</div>
				<div className="bm-repeater__item-actions">
					{ moveRemoveActions }
				</div>
			</div>

			<div className="bm-repeater__item-footer">
				<div className="bm-repeater__item-footer-edit">
					{ linkEditor }
					{ iconPicker }
					{ designButton }
				</div>
				{ placementChrome ? (
					<div className="bm-repeater__item-footer-end">
						<ToggleControl
							className="bm-repeater__btn-visibility"
							label={
								isHidden
									? __( 'Hidden', 'joc-blockora-morph' )
									: __( 'Visible', 'joc-blockora-morph' )
							}
							checked={ ! isHidden }
							onChange={ ( next ) =>
								onChange( {
									...item,
									hidden: ! next,
								} )
							}
							__nextHasNoMarginBottom
						/>
					</div>
				) : null }
			</div>
		</div>
	);
}

/**
 * @param {object}   props
 * @param {string}   props.label
 * @param {Array}    props.value
 * @param {Function} props.onChange
 * @param {object}   props.field
 * @param {Function} [props.onEditStyles] (index) => void — open Customize for this button
 * @param {boolean}  [props.allowManageButtonItems] Add/remove buttons (My Blocks). False in Gutenberg/FE.
 * @param {Function} [props.onFocusItem] (index) => void — select / focus item in preview
 */
export default function RepeaterField( {
	label,
	value,
	onChange,
	field = {},
	onEditStyles,
	allowManageButtonItems = true,
	onFocusItem,
	previewScope = null,
} ) {
	const items = Array.isArray( value ) ? value : [];
	const itemFields = Array.isArray( field.fields ) ? field.fields : [];
	const itemType = field.itemType || '';
	const isButtons =
		itemType === 'button' ||
		( ! itemType &&
			itemFields.length === 1 &&
			itemFields[ 0 ]?.type === 'link' );
	const isLogos = itemType === 'logo';
	const isSlides = itemType === 'slide';
	/** Hero-slider accordion UX for all structured lists (slides, cards, tabs, …). */
	const isCollection = ! isButtons && ! isLogos;
	/** Flat reorderable rows (no expand) — tabs / cards Fields list. */
	const isFlatList =
		isCollection &&
		( itemType === 'tab' ||
			itemType === 'card' ||
			field.listMode === 'flat' ||
			field.list_mode === 'flat' );
	const isAccordion = itemType === 'accordion';
	const minItems = Math.max(
		0,
		Number( field.minItems ) ||
			( isSlides
				? MIN_HERO_SLIDES
				: isButtons || isFlatList || isAccordion
					? collectionMinItems( field )
					: 0 )
	);
	const singleLinkField =
		isButtons &&
		itemFields.length === 1 &&
		itemFields[ 0 ]?.type === 'link';
	const canEditStyles = isButtons && typeof onEditStyles === 'function';
	const canManageButtons = ! isButtons || allowManageButtonItems;
	const canRemoveItem = canManageButtons && items.length > minItems;
	const canDrag = ( isFlatList || isAccordion ) && items.length > 1;
	const [ addSlideLayout, setAddSlideLayout ] = useState( 'simple' );
	/** Collection items are long — collapse by default; one open at a time. */
	const [ expandedItem, setExpandedItem ] = useState( null );
	const [ dragIndex, setDragIndex ] = useState( null );
	const [ overIndex, setOverIndex ] = useState( null );
	const [ pendingDeleteIndex, setPendingDeleteIndex ] = useState( null );
	const confirmDeletes =
		itemType === 'tab' ||
		itemType === 'card' ||
		itemType === 'accordion';
	const removeConfirmCopy = collectionRemoveConfirmCopy( field );
	const itemNoun = collectionItemNoun( field );
	const movingLabel = sprintf(
		/* translators: %s: Card, Tab, Item, … */
		__( 'Moving %s', 'joc-blockora-morph' ),
		itemNoun.toLowerCase()
	);

	const updateItem = ( index, nextItem ) => {
		const next = items.map( ( it, i ) => ( i === index ? nextItem : it ) );
		onChange( next );
	};

	const removeItem = ( index ) => {
		if ( ! canRemoveItem ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		onChange( items.filter( ( _, i ) => i !== index ) );
		setExpandedItem( ( prev ) => {
			if ( prev == null ) {
				return null;
			}
			if ( prev === index ) {
				return null;
			}
			return prev > index ? prev - 1 : prev;
		} );
	};

	const requestRemoveItem = ( index ) => {
		if ( ! canRemoveItem ) {
			return;
		}
		if ( confirmDeletes ) {
			setPendingDeleteIndex( index );
			return;
		}
		removeItem( index );
	};

	const move = ( index, dir ) => {
		const target = index + dir;
		if ( target < 0 || target >= items.length ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		const next = reorderCollectionArray( items, index, target );
		onChange( next );
		setExpandedItem( ( prev ) => {
			if ( prev === index ) {
				return target;
			}
			if ( prev === target ) {
				return index;
			}
			return prev;
		} );
	};

	const reorder = ( from, to ) => {
		const next = reorderCollectionArray( items, from, to );
		if ( next === items ) {
			return;
		}
		endPreviewContentEditForLinkEditor( {
			pauseCollectionWrites: true,
		} );
		onChange( next );
		setExpandedItem( ( prev ) => {
			if ( prev == null || from === to ) {
				return prev;
			}
			if ( prev === from ) {
				return to;
			}
			if ( from < to && prev > from && prev <= to ) {
				return prev - 1;
			}
			if ( from > to && prev >= to && prev < from ) {
				return prev + 1;
			}
			return prev;
		} );
	};

	const reorderFromDrop = ( from, over ) => {
		if (
			from == null ||
			over == null ||
			! Number.isFinite( from ) ||
			! Number.isFinite( over ) ||
			from === over
		) {
			return;
		}
		reorder( from, over );
	};

	const addItem = () => {
		if ( isButtons && ! allowManageButtonItems ) {
			return;
		}
		if ( isSlides ) {
			const nextIndex = items.length;
			onChange( [
				...items,
				createDefaultHeroSlide( { layout: addSlideLayout } ),
			] );
			setExpandedItem( nextIndex );
			return;
		}
		const nextIndex = items.length;
		const template =
			Array.isArray( field?.default ) &&
			field.default[ 0 ] &&
			typeof field.default[ 0 ] === 'object'
				? JSON.parse( JSON.stringify( field.default[ 0 ] ) )
				: null;
		const nextItem =
			isButtons && template
				? createDefaultHeroButton( {
						...template,
						icon:
							template.icon != null
								? String( template.icon )
								: '',
				  } )
				: defaultItem( itemFields, itemType, items.length );
		// Allow explicitly empty icon on card CTAs (template.icon: "").
		if (
			isButtons &&
			template &&
			Object.prototype.hasOwnProperty.call( template, 'icon' ) &&
			! String( template.icon || '' ).trim()
		) {
			nextItem.icon = '';
		}
		const seededItem = collectionSharesIcon( field )
			? applySharedCollectionIcon( nextItem, field, items )
			: nextItem;
		onChange( [ ...items, seededItem ] );
		if ( isFlatList ) {
			onFocusItem?.( nextIndex );
			return;
		}
		if ( isCollection ) {
			setExpandedItem( nextIndex );
		}
	};

	const toggleItem = ( index ) => {
		setExpandedItem( ( prev ) => ( prev === index ? null : index ) );
	};

	const addLogoFromMedia = ( selected ) => {
		onChange( [
			...items,
			{ image: mediaFromSelection( selected ) },
		] );
	};

	const pendingDeleteLabel = String(
		items[ pendingDeleteIndex ]?.tabTitle ||
			items[ pendingDeleteIndex ]?.accordionTitle ||
			items[ pendingDeleteIndex ]?.title ||
			''
	)
		.replace( /<[^>]+>/g, ' ' )
		.replace( /\s+/g, ' ' )
		.trim();

	return (
		<>
		<div
			className={
				'bm-field bm-repeater' +
				( isButtons ? ' bm-repeater--buttons' : '' ) +
				( isLogos ? ' bm-repeater--logos' : '' ) +
				( isFlatList ? ' bm-repeater--flat' : '' )
			}
		>
			{ label || isLogos ? (
				<div className="bm-repeater__head">
					<span className="bm-field-label">
						{ label || __( 'Logos', 'joc-blockora-morph' ) }
					</span>
					{ items.length ? (
						<span className="bm-repeater__count">{ items.length }</span>
					) : null }
					{ isLogos ? (
						<BmMediaUpload
							onSelect={ addLogoFromMedia }
							allowedTypes={ [ 'image' ] }
							title={
								field.addLabel ||
								__( 'Add logo', 'joc-blockora-morph' )
							}
							render={ ( { open } ) => (
								<Button
									size="small"
									variant="secondary"
									className="bm-repeater__add-inline"
									icon="plus-alt2"
									onClick={ open }
									aria-label={
										field.addLabel ||
										__( 'Add logo', 'joc-blockora-morph' )
									}
									label={
										field.addLabel ||
										__( 'Add logo', 'joc-blockora-morph' )
									}
									showTooltip
								/>
							) }
						/>
					) : null }
				</div>
			) : null }
			{ isButtons ? (
				<p className="bm-repeater__hint">
					{ allowManageButtonItems
						? __(
								'Add links and optional icons here. Use the art button to open Colors & hover in Customize.',
								'joc-blockora-morph'
						  )
						: __(
								'Edit each button’s link and icon here. Use Visible to show or hide it on this page only. Design appears after Customize this instance.',
								'joc-blockora-morph'
						  ) }
				</p>
			) : null }
			{ ( isFlatList || isAccordion ) && canDrag ? (
				<p className="bm-repeater__hint">
					{ itemType === 'card'
						? __(
								'Drag a card to reorder, or use the arrows. Edit content in the preview.',
								'joc-blockora-morph'
						  )
						: isAccordion
							? __(
									'Drag an item to reorder, or use the arrows.',
									'joc-blockora-morph'
							  )
							: __(
									'Drag an item to reorder, or use the arrows. Edit title and content in the preview.',
									'joc-blockora-morph'
							  ) }
				</p>
			) : isFlatList ? (
				<p className="bm-repeater__hint">
					{ itemType === 'card'
						? __(
								'Edit content in the preview. Use + to add a card.',
								'joc-blockora-morph'
						  )
						: __(
								'Edit title and content in the preview.',
								'joc-blockora-morph'
						  ) }
				</p>
			) : null }

			{ items.length ? (
				<div
					className={
						'bm-repeater__list' +
						( isLogos ? ' bm-repeater__list--logos' : '' )
					}
				>
					{ items.map( ( item, index ) => {
						if ( isLogos ) {
							return (
								<LogoRepeaterItem
									key={ index }
									item={ item }
									onChange={ ( next ) =>
										updateItem( index, next )
									}
									onRemove={ () =>
										onChange(
											items.filter( ( _, i ) => i !== index )
										)
									}
								/>
							);
						}
						if ( isButtons ) {
							return (
								<ButtonRepeaterItem
									key={ index }
									item={ item }
									index={ index }
									total={ items.length }
									onChange={ ( next ) =>
										updateItem( index, next )
									}
									onMove={ ( dir ) => move( index, dir ) }
									canRemove={ canRemoveItem }
									onDuplicate={
										canManageButtons
											? () => {
													const result =
														duplicateHeroButtonAt(
															items,
															index
														);
													if ( result ) {
														onChange( result.next );
													}
											  }
											: undefined
									}
									onRemove={
										canManageButtons
											? () => removeItem( index )
											: undefined
									}
									onEditStyles={
										canEditStyles ? onEditStyles : undefined
									}
									placementChrome={ ! allowManageButtonItems }
									previewScope={ previewScope }
								/>
							);
						}

						const itemLines = collectionItemLines(
							item,
							index,
							field,
							itemType
						);
						const itemTitle = itemLines.heading;
						const itemMeta = itemLines.meta;
						const itemOpen =
							! isFlatList &&
							isCollection &&
							expandedItem === index;
						const layoutName = isSlides
							? slideLayoutLabel( item?.layout )
							: '';
						const isDragging = dragIndex === index;
						const isOver =
							canDrag &&
							overIndex === index &&
							dragIndex !== index;

						return (
							<div
								key={ index }
								className={
									'bm-repeater__item' +
									( isCollection
										? ' bm-repeater__item--slide' +
										  ( isFlatList
												? ' bm-repeater__item--flat is-collapsed'
												: itemOpen
													? ' is-open'
													: ' is-collapsed' )
										: '' ) +
									( canDrag ? ' is-draggable' : '' ) +
									( isDragging ? ' is-dragging' : '' ) +
									( isOver ? ' is-drop-target' : '' )
								}
								draggable={ canDrag }
								onMouseDown={
									canDrag
										? ( e ) => {
												if (
													! isInteractiveTarget(
														e.target
													)
												) {
													e.stopPropagation();
												}
										  }
										: undefined
								}
								onDragStart={
									canDrag
										? ( e ) => {
												if (
													isInteractiveTarget(
														e.target
													)
												) {
													e.preventDefault();
													return;
												}
												e.stopPropagation();
												setDragIndex( index );
												e.dataTransfer.effectAllowed =
													'move';
												try {
													e.dataTransfer.setData(
														'application/x-bm-collection-index',
														String( index )
													);
													e.dataTransfer.setData(
														'text/plain',
														String( index )
													);
													e.dataTransfer.setData(
														'text/html',
														''
													);
												} catch ( err ) {
													e.dataTransfer.setData(
														'text/plain',
														String( index )
													);
												}
												const ghost =
													document.createElement(
														'div'
													);
												ghost.className =
													'bm-collection-drag-ghost bm-collection-drag-ghost--fields';
												ghost.innerHTML = `<span class="dashicons dashicons-move" aria-hidden="true"></span><span><strong>${ movingLabel }</strong><em>${ __(
													'Drop on another item to place it',
													'joc-blockora-morph'
												) }</em></span>`;
												ghost.style.cssText =
													'position:fixed;top:-1000px;left:-1000px;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;border:2px solid var(--bm-f-primary,#3525cd);background:#fff;box-shadow:0 10px 28px rgba(15,23,42,.18);font:600 13px/1.3 system-ui,sans-serif;color:#0f172a;z-index:2147483646;pointer-events:none;';
												document.body.appendChild(
													ghost
												);
												try {
													e.dataTransfer.setDragImage(
														ghost,
														20,
														18
													);
												} catch ( err ) {
													if (
														e.currentTarget instanceof
														HTMLElement
													) {
														e.dataTransfer.setDragImage(
															e.currentTarget,
															24,
															24
														);
													}
												}
												setTimeout(
													() => ghost.remove(),
													0
												);
										  }
										: undefined
								}
								onDragEnd={
									canDrag
										? () => {
												setDragIndex( null );
												setOverIndex( null );
										  }
										: undefined
								}
								onDragOver={
									canDrag
										? ( e ) => {
												e.preventDefault();
												e.stopPropagation();
												e.dataTransfer.dropEffect =
													'move';
												if ( overIndex !== index ) {
													setOverIndex( index );
												}
										  }
										: undefined
								}
								onDragLeave={
									canDrag
										? () => {
												if ( overIndex === index ) {
													setOverIndex( null );
												}
										  }
										: undefined
								}
								onDrop={
									canDrag
										? ( e ) => {
												e.preventDefault();
												e.stopPropagation();
												const fromRaw =
													e.dataTransfer.getData(
														'application/x-bm-collection-index'
													) ||
													e.dataTransfer.getData(
														'text/plain'
													);
												const from =
													fromRaw !== '' &&
													/^\d+$/.test(
														String( fromRaw ).trim()
													)
														? parseInt( fromRaw, 10 )
														: dragIndex;
												reorderFromDrop( from, index );
												setDragIndex( null );
												setOverIndex( null );
										  }
										: undefined
								}
							>
								{ isDragging ? (
									<span
										className="bm-repeater__drag-placeholder"
										aria-hidden="true"
									>
										<span className="dashicons dashicons-move" />
										{ movingLabel }
									</span>
								) : null }
								<div className="bm-repeater__item-header">
									{ isFlatList ? (
										<>
											{ canDrag ? (
												<span
													className="bm-repeater__drag-handle"
													title={ __(
														'Drag to reorder',
														'joc-blockora-morph'
													) }
													aria-hidden="true"
												>
													<span className="dashicons dashicons-menu" />
												</span>
											) : null }
											<button
												type="button"
												className="bm-repeater__item-toggle"
												draggable={ false }
												onClick={ () =>
													onFocusItem?.( index )
												}
											>
												<span
													className="bm-repeater__index"
													aria-hidden="true"
												>
													{ index + 1 }
												</span>
												<span className="bm-repeater__item-summary">
													<strong className="bm-repeater__item-title">
														{ itemTitle }
													</strong>
													{ itemMeta ? (
														<span className="bm-repeater__item-meta">
															{ itemMeta }
														</span>
													) : null }
												</span>
											</button>
										</>
									) : isCollection ? (
										<>
											{ canDrag ? (
												<span
													className="bm-repeater__drag-handle"
													title={ __(
														'Drag to reorder',
														'joc-blockora-morph'
													) }
													aria-hidden="true"
												>
													<span className="dashicons dashicons-menu" />
												</span>
											) : null }
											<button
												type="button"
												className="bm-repeater__item-toggle"
												draggable={ false }
												aria-expanded={ itemOpen }
												onClick={ () =>
													toggleItem( index )
												}
											>
												<span
													className="bm-repeater__index"
													aria-hidden="true"
												>
													{ index + 1 }
												</span>
												<span className="bm-repeater__item-summary">
													<strong className="bm-repeater__item-title">
														{ itemTitle }
													</strong>
													{ layoutName ? (
														<span className="bm-repeater__item-meta">
															{ layoutName }
														</span>
													) : null }
													{ itemMeta ? (
														<span className="bm-repeater__item-meta">
															{ itemMeta }
														</span>
													) : null }
												</span>
												<span
													className={
														'dashicons bm-repeater__item-chevron' +
														( itemOpen
															? ' dashicons-arrow-up-alt2'
															: ' dashicons-arrow-down-alt2' )
													}
													aria-hidden="true"
												/>
											</button>
										</>
									) : (
										<>
											<span
												className="bm-repeater__index"
												aria-hidden="true"
											>
												{ index + 1 }
											</span>
											<div className="bm-repeater__item-summary">
												<strong className="bm-repeater__item-title">
													{ itemTitle }
												</strong>
												{ itemMeta ? (
													<span className="bm-repeater__item-meta">
														{ itemMeta }
													</span>
												) : null }
											</div>
										</>
									) }
									<div className="bm-repeater__item-actions">
										<Button
											size="small"
											variant="tertiary"
											className="bm-repeater__icon-btn"
											disabled={ index === 0 }
											draggable={ false }
											onClick={ () => move( index, -1 ) }
											aria-label={ __(
												'Move up',
												'joc-blockora-morph'
											) }
											icon="arrow-up-alt2"
										/>
										<Button
											size="small"
											variant="tertiary"
											className="bm-repeater__icon-btn"
											disabled={
												index === items.length - 1
											}
											draggable={ false }
											onClick={ () => move( index, 1 ) }
											aria-label={ __(
												'Move down',
												'joc-blockora-morph'
											) }
											icon="arrow-down-alt2"
										/>
										{ canRemoveItem ? (
											<Button
												size="small"
												variant="tertiary"
												className="bm-repeater__icon-btn bm-repeater__icon-btn--danger"
												isDestructive
												draggable={ false }
												onClick={ () =>
													requestRemoveItem( index )
												}
												aria-label={
													isSlides
														? __(
																'Remove slide',
																'joc-blockora-morph'
														  )
														: itemType === 'tab'
															? __(
																	'Remove tab',
																	'joc-blockora-morph'
															  )
															: __(
																	'Remove item',
																	'joc-blockora-morph'
															  )
												}
												icon="no-alt"
											/>
										) : null }
									</div>
								</div>
								{ ! isFlatList &&
								( ! isCollection || itemOpen ) ? (
									<div className="bm-repeater__item-body">
										{ itemFields.map( ( f ) => (
											<ItemField
												key={ f.key }
												field={ f }
												hideLabel={ singleLinkField }
												value={ item[ f.key ] }
												onChange={ ( next ) =>
													updateItem( index, {
														...item,
														[ f.key ]: next,
													} )
												}
												allowManageButtonItems={
													allowManageButtonItems
												}
											/>
										) ) }
									</div>
								) : null }
							</div>
						);
					} ) }
				</div>
			) : (
				<div className="bm-repeater__empty">
					<span
						className={
							'dashicons ' +
							( isLogos
								? 'dashicons-images-alt2'
								: 'dashicons-button' )
						}
						aria-hidden="true"
					/>
					<p>
						{ isLogos
							? __(
									'No logos yet. Add one to get started.',
									'joc-blockora-morph'
							  )
							: isButtons
							? allowManageButtonItems
								? __(
										'No buttons yet. Add one to get started.',
										'joc-blockora-morph'
								  )
								: __(
										'No buttons on this block. Add them in My Blocks first.',
										'joc-blockora-morph'
								  )
							: isSlides
							? __(
									'No slides yet. Add at least two slides for the carousel.',
									'joc-blockora-morph'
							  )
							: itemType === 'tab'
							? __(
									'No tabs yet. Add one to get started.',
									'joc-blockora-morph'
							  )
							: isCollection
							? __(
									'No items yet. Add one to get started.',
									'joc-blockora-morph'
							  )
							: __(
									'No items yet. Add one to get started.',
									'joc-blockora-morph'
							  ) }
					</p>
				</div>
			) }

			{ ! isLogos && ( ! isButtons || allowManageButtonItems ) ? (
				isSlides ? (
					<div className="bm-repeater__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-repeater__add"
							icon="plus-alt2"
							onClick={ addItem }
						>
							{ field.addLabel ||
								__( 'Add slide', 'joc-blockora-morph' ) }
						</Button>
					</div>
				) : (
					<Button
						variant="secondary"
						className="bm-repeater__add"
						icon="plus-alt2"
						onClick={ addItem }
					>
						{ field.addLabel ||
							( isButtons
								? __( 'Add button', 'joc-blockora-morph' )
								: itemType === 'tab'
									? __( 'Add tab', 'joc-blockora-morph' )
									: itemType === 'card'
										? __( 'Add card', 'joc-blockora-morph' )
										: __( 'Add item', 'joc-blockora-morph' ) ) }
					</Button>
				)
			) : null }
		</div>
		{ confirmDeletes ? (
			<ConfirmModal
				open={ pendingDeleteIndex != null }
				title={ removeConfirmCopy.title }
				description={ removeConfirmCopy.description(
					pendingDeleteLabel || removeConfirmCopy.fallbackName
				) }
				icon="trash"
				tone="danger"
				confirmLabel={ removeConfirmCopy.confirmLabel }
				onCancel={ () => setPendingDeleteIndex( null ) }
				onConfirm={ () => {
					const index = pendingDeleteIndex;
					setPendingDeleteIndex( null );
					if ( index != null ) {
						removeItem( index );
					}
				} }
			/>
		) : null }
		</>
	);
}
