import { createContext, type ReactNode, type RefObject, useContext } from 'react';

import { Popover } from '@base-ui/react/popover';

import { useConstant } from '#/lib/hooks/use-constant';

import { emojiInserted } from '#/components/EmojiPicker/emoji-inserted';
import { useEmojiPreload } from '#/components/EmojiPicker/preload';
import type { Emoji } from '#/components/EmojiPicker/types';

import { m } from '#/paraglide/messages';

import { EmojiPanel } from './EmojiPanel';
import * as styles from './EmojiPicker.css';

export type { Emoji } from '#/components/EmojiPicker/types';

type FocusableElement = { focus: () => void };
type NextFocusRef = RefObject<FocusableElement | null> | (() => FocusableElement | null | undefined);

const EmojiPickerContext = createContext<{
	onEmojiSelect: (emoji: Emoji) => void;
	close: () => void;
	nextFocusRef?: NextFocusRef;
} | null>(null);

export type RootProps = {
	children: ReactNode;
	/** Detached handle linking the {@link Trigger} to this Root (see Base UI Popover detached triggers). */
	handle: EmojiPickerHandle;
	/**
	 * Called when the user selects an emoji. Fires in addition to the `emojiInserted` event, so callers that
	 * only need the text insertion can omit it.
	 */
	onEmojiSelect?: (emoji: Emoji) => void;
	/** Element to return focus to when the picker closes (instead of the trigger). Ref or getter. */
	nextFocusRef?: NextFocusRef;
};

/** The trigger button (a sibling of {@link Root}). Pass a web `Button` via `render` + the shared `handle`. */
export const Trigger = Popover.Trigger;

/** Creates a detached handle to associate a {@link Trigger} with a {@link Root} / open-close imperatively. */
const createHandle = Popover.createHandle;

/** A detached handle for the emoji picker popover. */
export type EmojiPickerHandle = Popover.Handle<void>;

/** Component-local emoji-picker handle. */
export function useEmojiPickerHandle(): EmojiPickerHandle {
	const handle = useConstant(createHandle<void>);
	return handle;
}

/**
 * emoji picker that opens in a popover. emits an `emojiInserted` event and triggers the optional
 * `onEmojiSelect` callback when an emoji is selected.
 */
export function Root({ children, handle, onEmojiSelect, nextFocusRef }: RootProps) {
	useEmojiPreload({ immediate: true });

	const value = {
		onEmojiSelect: (emoji: Emoji) => {
			emojiInserted.emit(emoji);
			onEmojiSelect?.(emoji);
		},
		close: () => handle.close(),
		nextFocusRef,
	};

	return (
		<EmojiPickerContext value={value}>
			<Popover.Root handle={handle} modal={true}>
				{children}
			</Popover.Root>
		</EmojiPickerContext>
	);
}

/** The picker panel. Must be rendered inside a {@link Root}. */
export function Picker() {
	const { onEmojiSelect, close, nextFocusRef } = useEmojiPickerContext();

	return (
		<Popover.Portal>
			<Popover.Positioner className={styles.positioner} sideOffset={5} collisionPadding={5}>
				<Popover.Popup
					className={styles.popup}
					// return focus to the caller's target (e.g. the composer text input) rather than the trigger
					finalFocus={() => {
						if (!nextFocusRef) {
							return;
						}
						const el = typeof nextFocusRef === 'function' ? nextFocusRef() : nextFocusRef.current;
						if (el) {
							el.focus();
							return false;
						}
					}}
				>
					<EmojiPanel
						onEmojiSelect={(emoji, shiftHeld) => {
							onEmojiSelect(emoji);
							if (!shiftHeld) {
								close();
							}
						}}
					/>
					<Popover.Close className={styles.srOnly}>{m['common.action.close']()}</Popover.Close>
				</Popover.Popup>
			</Popover.Positioner>
		</Popover.Portal>
	);
}

function useEmojiPickerContext() {
	const ctx = useContext(EmojiPickerContext);
	if (!ctx) {
		throw new Error('EmojiPicker.Picker must be used within an EmojiPicker.Root component');
	}
	return ctx;
}
