import React, {
	cloneElement,
	isValidElement,
	useMemo,
	useState,
	type CSSProperties,
	type HTMLProps,
	type JSX,
	type ReactElement,
	type ReactNode,
} from 'react';
import { autoUpdate } from '@floating-ui/dom';
import type { Placement } from '@floating-ui/react';
import {
	flip,
	FloatingFocusManager,
	FloatingNode,
	FloatingPortal,
	offset,
	shift,
	useClick,
	useDismiss,
	useFloating,
	useFloatingNodeId,
	useId,
	useInteractions,
	useRole,
} from '@floating-ui/react';
import type { FlexRenderable } from '@wener/reaction';
import { flexRender, mergeRefs, useControllable } from '@wener/reaction';
import { clsx } from 'clsx';
import type { UseFloatingInteractionsOptions } from './useFloatingInteractions';
import { useFloatingInteractions } from './useFloatingInteractions';

export interface PopoverProps extends UseFloatingInteractionsOptions {
	placement?: Placement;
	children: JSX.Element;

	modal?: boolean;
	open?: boolean;
	onOpenChange?: (v: boolean) => void;
	content?: FlexRenderable<PopoverContentProps>;

	offset?: number;
	shift?: Parameters<typeof shift>[0];

	portal?: boolean;
	className?: string;
	style?: CSSProperties;
}

export interface PopoverContentProps {
	close: () => void;
	labelId: string;
	descriptionId: string;
}

export const Popover = ({
	children,
	placement,
	content,
	portal,
	className,
	style,
	click = true,
	dismiss = true,
	hover = false,
	role = 'dialog',
	focus,
	modal,
	offset: _offset = 5,
	shift: _shift,
	...props
}: PopoverProps) => {
	const [open, setOpen] = useControllable(props.open, props.onOpenChange, false);

	const { x, y, refs, strategy, context } = useFloating({
		open,
		onOpenChange: setOpen,
		middleware: [offset(_offset), flip(), shift(_shift)],
		placement,
		whileElementsMounted: autoUpdate,
	});

	const id = useId();
	const labelId = `${id}-label`;
	const descriptionId = `${id}-description`;

	const { getReferenceProps, getFloatingProps } = useFloatingInteractions(context, {
		click,
		dismiss,
		hover,
		focus,
		role,
	});

	// Preserve the consumer's ref
	const ref = useMemo(() => mergeRefs(refs.setReference, (children as any).ref), [refs.setReference, children]);

	const pop = open && (
		<FloatingFocusManager context={context} modal={modal} order={['reference', 'content']} returnFocus={true}>
			<div
				ref={refs.setFloating}
				className={clsx('Popover', className)}
				style={{
					...style,
					position: strategy,
					top: y ?? 0,
					left: x ?? 0,
				}}
				aria-labelledby={labelId}
				aria-describedby={descriptionId}
				{...getFloatingProps()}
			>
				{!open
					? null
					: flexRender(content, {
							close: () => {
								setOpen(false);
							},
							labelId,
							descriptionId,
						})}
			</div>
		</FloatingFocusManager>
	);
	return (
		<>
			{cloneElement(children, getReferenceProps({ ref, ...children.props }))}
			{open && !portal && pop}
			<FloatingPortal>{open && portal && pop}</FloatingPortal>
		</>
	);
};

interface Props {
	render: (data: { close: () => void; labelId: string; descriptionId: string }) => ReactNode;
	placement?: Placement;
	modal?: boolean;
	children?: ReactElement<HTMLElement>;
	bubbles?: boolean;
}

function PopoverComponent({ children, render, placement, modal = true, bubbles = true }: Props) {
	const [open, setOpen] = useState(false);

	const nodeId = useFloatingNodeId();
	const { floatingStyles, refs, context } = useFloating({
		nodeId,
		open,
		placement,
		onOpenChange: setOpen,
		middleware: [offset(10), flip(), shift()],
		whileElementsMounted: autoUpdate,
	});

	const id = useId();
	const labelId = `${id}-label`;
	const descriptionId = `${id}-description`;

	const { getReferenceProps, getFloatingProps } = useInteractions([
		useClick(context),
		useRole(context),
		useDismiss(context, {
			bubbles,
		}),
	]);

	return (
		<FloatingNode id={nodeId}>
			{isValidElement(children) &&
				cloneElement(
					children,
					getReferenceProps({
						ref: refs.setReference,
						'data-open': open ? '' : undefined,
					} as HTMLProps<Element>),
				)}
			<FloatingPortal>
				{open && (
					<FloatingFocusManager context={context} modal={modal}>
						<div
							className='rounded border border-slate-900/10 bg-white bg-clip-padding px-4 py-6 shadow-md'
							ref={refs.setFloating}
							style={floatingStyles}
							aria-labelledby={labelId}
							aria-describedby={descriptionId}
							{...getFloatingProps()}
						>
							{render({
								labelId,
								descriptionId,
								close: () => setOpen(false),
							})}
						</div>
					</FloatingFocusManager>
				)}
			</FloatingPortal>
		</FloatingNode>
	);
}

/*
export function Popover(props: Props) {
  const parentId = useFloatingParentNodeId();

  // This is a root, so we wrap it with the tree
  if (parentId === null) {
    return (
      <FloatingTree>
        <PopoverComponent {...props} />
      </FloatingTree>
    );
  }

  return <PopoverComponent {...props} />;
}
 */
