'use no memo'; // controlled input updates usually invalidate the generated caches

import { type ComponentPropsWithRef, type MouseEvent, type ReactNode, type Ref, useRef } from 'react';

import { clsx } from 'clsx';

import { mergeRefs } from '#/lib/utils/merge-refs';

import * as styles from '#/components/forms/SearchField.css';
import { Button, ButtonIcon } from '#/components/web/Button';

import XIcon from '#/icons/central/CrossLarge_round_outlined_radius1_stroke2.svg';
import MagnifyingGlassIcon from '#/icons/central/MagnifyingGlass_round_outlined_radius1_stroke2.svg';

// clicks landing on one of these are handled by the element itself; the field must not steal them to refocus
// the input. `input`/`textarea` are listed so a direct click keeps native caret placement.
const INTERACTIVE_SELECTOR = 'a, button, input, select, textarea, [role="button"], [role="link"]';

/**
 * search-field chrome: a flex container laying out a leading {@link Icon}, the {@link Input}, and trailing
 * controls ({@link Clear} and/or a {@link Slot}). clicking anywhere on the box that isn't an interactive
 * control focuses the input.
 */
export function Root({
	children,
	className,
	ref,
}: {
	children: ReactNode;
	className?: string;
	ref?: Ref<HTMLDivElement>;
}) {
	const innerRef = useRef<HTMLDivElement>(null);
	// the input no longer fills the box, so restore "click anywhere to focus" over the padding and the
	// non-interactive icon. mousedown (not click) so focus lands before a selection can start and without a
	// flicker; preventDefault keeps the click from moving focus off the input we're about to focus.
	const onMouseDown = (event: MouseEvent<HTMLDivElement>) => {
		const target = event.target;
		if (event.defaultPrevented || !(target instanceof Element) || target.closest(INTERACTIVE_SELECTOR)) {
			return;
		}
		event.preventDefault();
		innerRef.current?.querySelector<HTMLElement>('input, textarea')?.focus();
	};

	return (
		<div className={clsx(styles.field, className)} onMouseDown={onMouseDown} ref={mergeRefs([innerRef, ref])}>
			{children}
		</div>
	);
}

/** leading, non-interactive magnifying-glass icon. */
export function Icon() {
	return <MagnifyingGlassIcon className={styles.icon} />;
}

/**
 * the styled field input. render it directly, or via a Base UI input's `render` prop to inherit the shared
 * styling.
 */
export function Input({ className, ...props }: ComponentPropsWithRef<'input'>) {
	return <input type="text" {...props} className={clsx(styles.input, className)} />;
}

/**
 * trailing clear button (×); out of the tab order since keyboard users clear by editing. usable standalone
 * (pass `onClick`) or as a Base UI `render` target (e.g. `<Autocomplete.Clear render={<Clear label={…}
 * />}>`), which injects the press/visibility behavior. `children` is fixed to the × icon.
 *
 * @param label accessible name
 */
export function Clear({
	className,
	label,
	...props
}: { label: string } & Omit<ComponentPropsWithRef<'button'>, 'children' | 'color'>) {
	return (
		<Button
			className={clsx(styles.clear, className)}
			color="secondary"
			label={label}
			shape="round"
			size="tiny"
			tabIndex={-1}
			variant="ghost"
			{...props}
		>
			<ButtonIcon icon={XIcon} size="xs" />
		</Button>
	);
}

/** trailing container grouping several controls (e.g. a {@link Clear} beside a persistent accessory). */
export function Slot({ children }: { children: ReactNode }) {
	return <div className={styles.slot}>{children}</div>;
}
