/**
 * Styled select — always a portaled dropdown (inspector-safe).
 * Long lists get a search field inside the menu; short lists stay simple.
 *
 * Never reparents ComboboxControl DOM (that caused removeChild crashes and
 * clipped/invisible menus inside overflow:hidden inspector panels).
 */
import {
	createPortal,
	useCallback,
	useEffect,
	useId,
	useMemo,
	useRef,
	useState,
} from '@wordpress/element';
import { __ } from '@wordpress/i18n';
// Styles: src/shared/shared-ui.js → build/shared-ui.css

const MENU_MAX_HEIGHT = 240;
const MENU_GAP = 4;
const DEFAULT_SEARCHABLE_THRESHOLD = 12;

/**
 * @param {HTMLElement} menu
 * @param {DOMRect|ClientRect} rect
 * @param {{ openUp: boolean, maxHeight: number, width: number }} opts
 */
function applyFixedMenuBox( menu, rect, { openUp, maxHeight, width } ) {
	const viewportH =
		window.innerHeight || document.documentElement.clientHeight || 0;
	const left = Math.max( 8, rect.left );
	menu.classList.add( 'bm-select-menu--fixed' );
	menu.style.setProperty( 'position', 'fixed', 'important' );
	menu.style.setProperty( 'left', `${ left }px`, 'important' );
	menu.style.setProperty( 'right', 'auto', 'important' );
	menu.style.setProperty( 'width', `${ Math.max( 0, width ) }px`, 'important' );
	menu.style.setProperty(
		'max-width',
		`${ Math.max( 0, width ) }px`,
		'important'
	);
	menu.style.setProperty( 'min-width', '0', 'important' );
	menu.style.setProperty( 'z-index', '1000500', 'important' );
	const toolbarSelect =
		menu.classList.contains( 'bm-format-toolbar__select-menu' ) ||
		Boolean( menu.closest?.( '.bm-format-toolbar__fonts-popover' ) );
	if ( toolbarSelect || menu.className.includes( 'bm-format-toolbar__select-menu' ) ) {
		menu.style.setProperty( 'z-index', '1001800', 'important' );
	}
	menu.style.setProperty( 'max-height', `${ maxHeight }px`, 'important' );
	menu.style.setProperty( 'box-sizing', 'border-box', 'important' );
	menu.style.setProperty( 'visibility', 'visible', 'important' );
	menu.style.setProperty( 'pointer-events', 'auto', 'important' );
	menu.style.removeProperty( 'display' );
	if ( openUp ) {
		menu.style.setProperty( 'top', 'auto', 'important' );
		menu.style.setProperty(
			'bottom',
			`${ Math.max( 8, viewportH - rect.top + MENU_GAP ) }px`,
			'important'
		);
		menu.classList.add( 'is-drop-up' );
	} else {
		menu.style.setProperty(
			'top',
			`${ rect.bottom + MENU_GAP }px`,
			'important'
		);
		menu.style.setProperty( 'bottom', 'auto', 'important' );
		menu.classList.remove( 'is-drop-up' );
	}
}

/**
 * @param {object}   props
 * @param {string}   [props.label]
 * @param {string}   [props.help]
 * @param {string}   [props.value]
 * @param {Function} props.onChange
 * @param {Array<{ label: string, value: string }>} props.options
 * @param {boolean}  [props.hideLabelFromVision]
 * @param {boolean}  [props.searchable]
 * @param {string}   [props.menuClassName] Extra class on the portaled menu
 * @param {number}   [props.menuMinWidth] Floor width for the portaled menu
 */
function StyledSelect( {
	label,
	help,
	value = '',
	onChange,
	options = [],
	hideLabelFromVision = false,
	searchable = false,
	menuClassName = '',
	menuMinWidth = 0,
	previewFontFamily = false,
} ) {
	const rootRef = useRef( null );
	const listRef = useRef( null );
	const searchRef = useRef( null );
	const onChangeRef = useRef( onChange );
	onChangeRef.current = onChange;
	const [ open, setOpen ] = useState( false );
	const [ query, setQuery ] = useState( '' );
	const listId = useId();
	const labelId = useId();
	const searchId = useId();

	const list = Array.isArray( options ) ? options : [];
	const selected = useMemo(
		() => list.find( ( o ) => String( o.value ) === String( value ) ),
		[ list, value ]
	);
	const displayLabel =
		selected?.label ||
		( value
			? String( value )
			: __( 'Select…', 'joc-blockora-morph' ) );

	const filtered = useMemo( () => {
		if ( ! searchable ) {
			return list;
		}
		const q = String( query || '' )
			.trim()
			.toLowerCase();
		if ( ! q ) {
			return list;
		}
		return list.filter( ( o ) => {
			const labelText = String( o.label || '' ).toLowerCase();
			const valueText = String( o.value || '' ).toLowerCase();
			return labelText.includes( q ) || valueText.includes( q );
		} );
	}, [ list, query, searchable ] );

	const placeMenu = useCallback( () => {
		const root = rootRef.current;
		const menu = listRef.current;
		if ( ! root || ! menu ) {
			return;
		}
		const toggle = root.querySelector( '.bm-styled-select__toggle' );
		if ( ! toggle ) {
			return;
		}
		const rect = toggle.getBoundingClientRect();
		const viewportH =
			window.innerHeight || document.documentElement.clientHeight || 0;
		const spaceBelow = Math.max( 0, viewportH - rect.bottom - MENU_GAP );
		const spaceAbove = Math.max( 0, rect.top - MENU_GAP );
		const needed = Math.min(
			MENU_MAX_HEIGHT,
			menu.scrollHeight || MENU_MAX_HEIGHT
		);
		const openUp = spaceBelow < needed && spaceAbove > spaceBelow;
		const available = openUp ? spaceAbove : spaceBelow;
		const maxHeight = Math.max(
			120,
			Math.min( MENU_MAX_HEIGHT, available )
		);
		root.classList.toggle( 'is-drop-up', openUp );
		const minW = Number( menuMinWidth );
		applyFixedMenuBox( menu, rect, {
			openUp,
			maxHeight,
			width: Math.max(
				rect.width,
				Number.isFinite( minW ) && minW > 0 ? minW : 0
			),
		} );
	}, [ menuMinWidth ] );

	useEffect( () => {
		if ( ! open ) {
			setQuery( '' );
			return undefined;
		}

		const run = () => placeMenu();
		run();
		const raf = window.requestAnimationFrame( run );
		const focusSearch = window.setTimeout( () => {
			if ( searchable ) {
				searchRef.current?.focus?.();
			}
		}, 0 );

		const eventInMenu = ( event ) => {
			const path =
				typeof event.composedPath === 'function'
					? event.composedPath()
					: [];
			if (
				path.some(
					( n ) =>
						n instanceof Element &&
						( n === listRef.current ||
							n.classList?.contains(
								'bm-styled-select__list--portal'
							) )
				)
			) {
				return true;
			}
			const t = event.target;
			if ( ! ( t instanceof Node ) ) {
				return false;
			}
			if ( listRef.current?.contains( t ) ) {
				return true;
			}
			return (
				t instanceof Element &&
				Boolean( t.closest( '.bm-styled-select__list--portal' ) )
			);
		};

		const onDocPointer = ( event ) => {
			if ( rootRef.current?.contains( event.target ) ) {
				return;
			}
			if ( eventInMenu( event ) ) {
				return;
			}
			setOpen( false );
		};
		const onKey = ( event ) => {
			if ( event.key === 'Escape' ) {
				setOpen( false );
			}
		};

		const onMenuPointerDown = ( event ) => {
			const t = event.target;
			if ( ! ( t instanceof Element ) ) {
				return;
			}
			// Don't steal focus from the search field.
			if ( t.closest( '.bm-styled-select__search' ) ) {
				return;
			}
			const row = t.closest( '[role="option"]' );
			if ( ! row || ! listRef.current?.contains( row ) ) {
				return;
			}
			event.preventDefault();
			event.stopPropagation();
			const next = row.getAttribute( 'data-bm-option-value' );
			setOpen( false );
			onChangeRef.current?.( next ?? '' );
		};

		const menuEl = listRef.current;
		menuEl?.addEventListener( 'pointerdown', onMenuPointerDown, true );

		document.addEventListener( 'pointerdown', onDocPointer, true );
		document.addEventListener( 'keydown', onKey );
		window.addEventListener( 'resize', placeMenu );
		window.addEventListener( 'scroll', placeMenu, true );
		return () => {
			window.cancelAnimationFrame( raf );
			window.clearTimeout( focusSearch );
			menuEl?.removeEventListener(
				'pointerdown',
				onMenuPointerDown,
				true
			);
			document.removeEventListener( 'pointerdown', onDocPointer, true );
			document.removeEventListener( 'keydown', onKey );
			window.removeEventListener( 'resize', placeMenu );
			window.removeEventListener( 'scroll', placeMenu, true );
		};
	}, [ open, placeMenu, searchable ] );

	useEffect( () => {
		if ( open ) {
			placeMenu();
		}
	}, [ open, filtered, placeMenu ] );

	const menu =
		open && typeof document !== 'undefined'
			? createPortal(
					<div
						ref={ listRef }
						id={ listId }
						className={
							'bm-styled-select__list bm-styled-select__list--portal bm-select-menu--fixed' +
							( searchable ? ' is-searchable' : '' ) +
							( menuClassName ? ` ${ menuClassName }` : '' )
						}
						role="listbox"
					>
						{ searchable ? (
							<div className="bm-styled-select__search">
								<input
									ref={ searchRef }
									id={ searchId }
									type="search"
									className="bm-styled-select__search-input"
									value={ query }
									placeholder={ __(
										'Search…',
										'joc-blockora-morph'
									) }
									aria-label={ __(
										'Search options',
										'joc-blockora-morph'
									) }
									autoComplete="off"
									onChange={ ( event ) =>
										setQuery( event.target.value )
									}
									onKeyDown={ ( event ) => {
										if ( event.key === 'Escape' ) {
											event.stopPropagation();
											setOpen( false );
										}
									} }
								/>
							</div>
						) : null }
						<ul className="bm-styled-select__options" role="presentation">
							{ filtered.length ? (
								filtered.map( ( opt ) => {
									const isSelected =
										String( opt.value ) === String( value );
									const fontPreview =
										previewFontFamily &&
										String( opt.value || '' ).trim() !== '';
									return (
										<li
											key={ String( opt.value ) }
											role="option"
											data-bm-option-value={ String(
												opt.value ?? ''
											) }
											aria-selected={ isSelected }
											className={
												'bm-styled-select__option' +
												( isSelected
													? ' is-selected'
													: '' ) +
												( fontPreview
													? ' is-font-preview'
													: '' )
											}
											style={
												fontPreview
													? {
															'--bm-option-font-family':
																opt.value,
															fontFamily:
																opt.value,
													  }
													: undefined
											}
										>
											{ opt.label }
										</li>
									);
								} )
							) : (
								<li
									className="bm-styled-select__option is-empty"
									aria-disabled="true"
								>
									{ __(
										'No matches',
										'joc-blockora-morph'
									) }
								</li>
							) }
						</ul>
					</div>,
					document.body
			  )
			: null;

	return (
		<div
			ref={ rootRef }
			className={
				`bm-searchable-select is-styled-select` +
				( searchable ? ' is-searchable' : '' ) +
				( open ? ' is-open' : '' )
			}
		>
			{ label && ! hideLabelFromVision ? (
				<label className="components-base-control__label" id={ labelId }>
					{ label }
				</label>
			) : null }
			{ label && hideLabelFromVision ? (
				<span className="screen-reader-text" id={ labelId }>
					{ label }
				</span>
			) : null }
			{ help ? (
				<p className="components-base-control__help">{ help }</p>
			) : null }
			<div className="bm-styled-select__shell">
				<button
					type="button"
					className="bm-styled-select__toggle"
					aria-haspopup="listbox"
					aria-expanded={ open }
					aria-controls={ listId }
					aria-labelledby={ label ? labelId : undefined }
					onMouseDown={ ( event ) => {
						event.preventDefault();
					} }
					onClick={ ( event ) => {
						event.stopPropagation();
						setOpen( ( v ) => ! v );
					} }
				>
					<span
						className={
							'bm-styled-select__value' +
							( previewFontFamily &&
							selected?.value &&
							String( selected.value ).trim() !== ''
								? ' is-font-preview'
								: '' )
						}
						style={
							previewFontFamily &&
							selected?.value &&
							String( selected.value ).trim() !== ''
								? {
										'--bm-option-font-family':
											selected.value,
										fontFamily: selected.value,
								  }
								: undefined
						}
					>
						{ displayLabel }
					</span>
				</button>
			</div>
			{ menu }
		</div>
	);
}

/**
 * @param {object}   props
 * @param {string}   [props.label]
 * @param {string}   [props.help]
 * @param {string}   [props.className]
 * @param {string}   props.value
 * @param {Function} props.onChange
 * @param {Array<{ label: string, value: string }>} props.options
 * @param {boolean}  [props.hideLabelFromVision]
 * @param {number}   [props.searchableThreshold] Search UI when options.length >= this
 * @param {string}   [props.menuClassName] Extra class on the portaled menu
 * @param {number}   [props.menuMinWidth] Floor width for the portaled menu
 * @param {boolean}  [props.previewFontFamily] Render labels in each option's font-family
 */
export default function SearchableSelect( {
	label,
	help,
	className = '',
	value = '',
	onChange,
	options = [],
	hideLabelFromVision = false,
	searchableThreshold = DEFAULT_SEARCHABLE_THRESHOLD,
	menuClassName = '',
	menuMinWidth = 0,
	previewFontFamily = false,
} ) {
	const list = Array.isArray( options ) ? options : [];
	const threshold = Number( searchableThreshold );
	const searchable =
		list.length >=
		( Number.isFinite( threshold ) && threshold > 0
			? threshold
			: DEFAULT_SEARCHABLE_THRESHOLD );

	return (
		<div className={ `bm-searchable-select-wrap ${ className }`.trim() }>
			<StyledSelect
				label={ label }
				help={ help }
				value={ value }
				onChange={ onChange }
				options={ list }
				hideLabelFromVision={ hideLabelFromVision }
				searchable={ searchable }
				menuClassName={ menuClassName }
				menuMinWidth={ menuMinWidth }
				previewFontFamily={ previewFontFamily }
			/>
		</div>
	);
}
