import type React from "react";
import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from "react";

import { useHandleClickOutside } from "@ax/hooks";

import * as S from "./style";

const CLOSED_POSITION = { openUpward: false, leftwardOffset: 0, isVisible: false };

const FloatingMenu = (props: IFloatingProps): JSX.Element => {
	const {
		children,
		isInAppBar,
		Button,
		position = "right",
		closeOnSelect = true,
		isCheckGroup,
		reactiveToHover,
		offset,
		hasMargin = true,
		fixedWidth,
		actionOnClose,
		disabled = false,
	} = props;
	const wrapperRef = useRef<HTMLDivElement | null>(null);
	const buttonRef = useRef<HTMLDivElement | null>(null);
	const menuOptionsRef = useRef<HTMLDivElement | null>(null);
	const [isOpen, setOpen] = useState(false);
	const [menuPosition, setMenuPosition] = useState(CLOSED_POSITION);

	useLayoutEffect(() => {
		if (!isOpen || !menuOptionsRef.current) return;

		const bounding = menuOptionsRef.current.getBoundingClientRect();
		const openUpward = bounding.bottom > document.documentElement.clientHeight;
		const rightOverflow = bounding.right - document.documentElement.clientWidth;
		const leftwardOffset = rightOverflow > 0 ? rightOverflow : 0;

		setMenuPosition({ openUpward, leftwardOffset, isVisible: true });
	}, [isOpen]);

	const closeMenu = useCallback(() => {
		actionOnClose?.();
		setOpen(false);
		setMenuPosition(CLOSED_POSITION);
	}, [actionOnClose]);

	const handleClickOutside = (e: MouseEvent) => {
		if (wrapperRef.current?.contains(e.target as HTMLElement)) {
			return;
		}

		closeMenu();
	};

	const closeWhenChecked = (e: React.MouseEvent<HTMLDivElement>) => {
		const target = e.target as HTMLInputElement;
		const isChecked = target?.checked;
		isChecked && closeMenu();
	};

	const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
		e.stopPropagation();
		if (disabled) return;
		const buttonWasClicked = buttonRef.current?.contains(e.target as HTMLElement);
		const optionWasSelected = isOpen && menuOptionsRef.current?.contains(e.target as HTMLElement);

		if (buttonWasClicked) {
			isOpen ? closeMenu() : setOpen(true);
		}

		if (optionWasSelected) {
			if ((e.target as HTMLElement).closest("[data-close-menu]")) {
				closeMenu();
			} else {
				closeOnSelect ? (isCheckGroup ? closeWhenChecked(e) : closeMenu()) : setOpen(true);
			}
		}
	};

	const handleMouseEnter = (e: React.MouseEvent<HTMLDivElement>) =>
		reactiveToHover && !isOpen ? handleClick(e) : null;

	const handleMouseLeave = () => reactiveToHover && isOpen && closeMenu();

	useHandleClickOutside(isOpen, handleClickOutside);

	return (
		<S.Wrapper
			ref={wrapperRef}
			onClick={handleClick}
			onMouseEnter={handleMouseEnter}
			onMouseLeave={handleMouseLeave}
			data-testid="floating-menu"
		>
			<S.ButtonWrapper ref={buttonRef} data-testid="floating-menu-button">
				<Button disabled={disabled} />
			</S.ButtonWrapper>

			{isOpen && (
				<S.MenuWrapper
					isInAppBar={isInAppBar}
					ref={menuOptionsRef}
					position={position}
					offset={offset}
					openUpward={menuPosition.openUpward}
					leftwardOffset={menuPosition.leftwardOffset}
					isVisible={menuPosition.isVisible}
					data-testid="floating-menu-wrapper"
				>
					<S.Menu hasMargin={hasMargin} width={fixedWidth}>
						{children}
					</S.Menu>
				</S.MenuWrapper>
			)}
		</S.Wrapper>
	);
};

export interface IFloatingProps {
	children: ReactNode;
	Button: (props?: any) => React.JSX.Element;
	isInAppBar?: boolean;
	position?: string;
	closeOnSelect?: boolean;
	isCheckGroup?: boolean;
	reactiveToHover?: boolean;
	offset?: number;
	hasMargin?: boolean;
	fixedWidth?: number;
	actionOnClose?(): void;
	disabled?: boolean;
}

export default FloatingMenu;
