'use client';

import '@/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss';
import { Separator } from '@/components/tiptap-ui-primitive/separator';
import type { Placement } from '@floating-ui/react';
import {
    autoUpdate,
    flip,
    FloatingFocusManager,
    FloatingList,
    FloatingPortal,
    offset,
    shift,
    useClick,
    useDismiss,
    useFloating,
    useInteractions,
    useListItem,
    useListNavigation,
    useMergeRefs,
    useRole,
    useTypeahead,
} from '@floating-ui/react';
import * as React from 'react';

interface DropdownMenuOptions {
    initialOpen?: boolean;
    open?: boolean;
    onOpenChange?: (open: boolean) => void;
    side?: 'top' | 'right' | 'bottom' | 'left';
    align?: 'start' | 'center' | 'end';
}

interface DropdownMenuProps extends DropdownMenuOptions {
    children: React.ReactNode;
}

type ContextType = ReturnType<typeof useDropdownMenu> & {
    updatePosition: (side: 'top' | 'right' | 'bottom' | 'left', align: 'start' | 'center' | 'end') => void;
};

const DropdownMenuContext = React.createContext<ContextType | null>(null);

function useDropdownMenuContext() {
    const context = React.useContext(DropdownMenuContext);
    if (!context) {
        throw new Error('DropdownMenu components must be wrapped in <DropdownMenu />');
    }
    return context;
}

function useDropdownMenu({
    initialOpen = false,
    open: controlledOpen,
    onOpenChange: setControlledOpen,
    side = 'bottom',
    align = 'start',
}: DropdownMenuOptions) {
    const [uncontrolledOpen, setUncontrolledOpen] = React.useState(initialOpen);
    const [currentPlacement, setCurrentPlacement] = React.useState<Placement>(`${side}-${align}` as Placement);
    const [activeIndex, setActiveIndex] = React.useState<number | null>(null);

    const open = controlledOpen ?? uncontrolledOpen;
    const setOpen = setControlledOpen ?? setUncontrolledOpen;

    const elementsRef = React.useRef<Array<HTMLElement | null>>([]);
    const labelsRef = React.useRef<Array<string | null>>([]);

    const floating = useFloating({
        open,
        onOpenChange: setOpen,
        placement: currentPlacement,
        middleware: [offset({ mainAxis: 4 }), flip(), shift({ padding: 4 })],
        whileElementsMounted: autoUpdate,
    });

    const { context } = floating;

    const interactions = useInteractions([
        useClick(context, {
            event: 'mousedown',
            toggle: true,
            ignoreMouse: false,
        }),
        useRole(context, { role: 'menu' }),
        useDismiss(context, {
            outsidePress: true,
            outsidePressEvent: 'mousedown',
        }),
        useListNavigation(context, {
            listRef: elementsRef,
            activeIndex,
            onNavigate: setActiveIndex,
            loop: true,
        }),
        useTypeahead(context, {
            listRef: labelsRef,
            onMatch: open ? setActiveIndex : undefined,
            activeIndex,
        }),
    ]);

    const updatePosition = React.useCallback((newSide: 'top' | 'right' | 'bottom' | 'left', newAlign: 'start' | 'center' | 'end') => {
        setCurrentPlacement(`${newSide}-${newAlign}` as Placement);
    }, []);

    return React.useMemo(
        () => ({
            open,
            setOpen,
            activeIndex,
            setActiveIndex,
            elementsRef,
            labelsRef,
            updatePosition,
            ...interactions,
            ...floating,
        }),
        [open, setOpen, activeIndex, interactions, floating, updatePosition],
    );
}

export function DropdownMenu({ children, ...options }: DropdownMenuProps) {
    const dropdown = useDropdownMenu(options);
    return (
        <DropdownMenuContext.Provider value={dropdown}>
            <FloatingList elementsRef={dropdown.elementsRef} labelsRef={dropdown.labelsRef}>
                {children}
            </FloatingList>
        </DropdownMenuContext.Provider>
    );
}

interface DropdownMenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    asChild?: boolean;
}

export const DropdownMenuTrigger = React.forwardRef<HTMLButtonElement, DropdownMenuTriggerProps>(
    ({ children, asChild = false, ...props }, propRef) => {
        const context = useDropdownMenuContext();
        const childrenRef = React.isValidElement(children)
            ? Number.parseInt(React.version, 10) >= 19
                ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
                  (children as { props: { ref?: React.Ref<any> } }).props.ref
                : // eslint-disable-next-line @typescript-eslint/no-explicit-any
                  (children as any).ref
            : undefined;
        const ref = useMergeRefs([context.refs.setReference, propRef, childrenRef]);

        if (asChild && React.isValidElement(children)) {
            const dataAttributes = {
                'data-state': context.open ? 'open' : 'closed',
            };

            return React.cloneElement(
                children,
                context.getReferenceProps({
                    ref,
                    ...props,
                    ...(typeof children.props === 'object' ? children.props : {}),
                    'aria-expanded': context.open,
                    'aria-haspopup': 'menu' as const,
                    ...dataAttributes,
                }),
            );
        }

        return (
            <button
                aria-expanded={context.open}
                aria-haspopup="menu"
                data-state={context.open ? 'open' : 'closed'}
                ref={ref}
                {...context.getReferenceProps(props)}
            >
                {children}
            </button>
        );
    },
);

DropdownMenuTrigger.displayName = 'DropdownMenuTrigger';

interface DropdownMenuContentProps extends React.HTMLAttributes<HTMLDivElement> {
    orientation?: 'vertical' | 'horizontal';
    side?: 'top' | 'right' | 'bottom' | 'left';
    align?: 'start' | 'center' | 'end';
    portal?: boolean;
    portalProps?: Omit<React.ComponentProps<typeof FloatingPortal>, 'children'>;
}

export const DropdownMenuContent = React.forwardRef<HTMLDivElement, DropdownMenuContentProps>(
    ({ style, className, orientation = 'vertical', side = 'bottom', align = 'start', portal = true, portalProps = {}, ...props }, propRef) => {
        const context = useDropdownMenuContext();
        const ref = useMergeRefs([context.refs.setFloating, propRef]);

        React.useEffect(() => {
            context.updatePosition(side, align);
        }, [context, side, align]);

        if (!context.open) return null;

        const content = (
            <FloatingFocusManager context={context.context} initialFocus={0} modal={false} returnFocus={true}>
                <div
                    aria-orientation={orientation}
                    className={`tiptap-dropdown-menu ${className || ''}`}
                    data-align={align}
                    data-orientation={orientation}
                    data-side={side}
                    data-state={context.open ? 'open' : 'closed'}
                    ref={ref}
                    style={{
                        position: context.strategy,
                        top: context.y ?? 0,
                        left: context.x ?? 0,
                        outline: 'none',
                        ...style,
                    }}
                    {...context.getFloatingProps(props)}
                >
                    {props.children}
                </div>
            </FloatingFocusManager>
        );

        if (portal) {
            return <FloatingPortal {...portalProps}>{content}</FloatingPortal>;
        }

        return content;
    },
);

DropdownMenuContent.displayName = 'DropdownMenuContent';

interface DropdownMenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
    asChild?: boolean;
    disabled?: boolean;
    onSelect?: () => void;
}

export const DropdownMenuItem = React.forwardRef<HTMLDivElement, DropdownMenuItemProps>(
    ({ children, disabled, asChild = false, onSelect, className, ...props }, ref) => {
        const context = useDropdownMenuContext();
        const item = useListItem({ label: disabled ? null : children?.toString() });
        const isActive = context.activeIndex === item.index;

        const handleSelect = React.useCallback(
            (event: React.MouseEvent<HTMLDivElement>) => {
                if (disabled) return;
                onSelect?.();
                props.onClick?.(event);
                context.setOpen(false);
            },
            [context, disabled, onSelect, props],
        );

        const itemProps: React.HTMLAttributes<HTMLDivElement> & {
            ref: React.Ref<HTMLDivElement>;
            role: string;
            tabIndex: number;
            'aria-disabled'?: boolean;
            'data-highlighted'?: boolean;
        } = {
            ref: useMergeRefs([item.ref, ref]),
            role: 'menuitem',
            className,
            tabIndex: isActive ? 0 : -1,
            'data-highlighted': isActive,
            'aria-disabled': disabled,
            ...context.getItemProps({
                ...props,
                onClick: handleSelect,
            }),
        };

        if (asChild && React.isValidElement(children)) {
            const childProps = children.props as {
                onClick?: (event: React.MouseEvent<HTMLElement>) => void;
            };

            // Create merged props without adding onClick directly to the props object
            const mergedProps = {
                ...itemProps,
                ...(typeof children.props === 'object' ? children.props : {}),
            };

            // Handle onClick separately based on the element type
            const eventHandlers = {
                onClick: (event: React.MouseEvent<HTMLElement>) => {
                    // Cast the event to make it compatible with handleSelect
                    handleSelect(event as unknown as React.MouseEvent<HTMLDivElement>);
                    childProps.onClick?.(event);
                },
            };

            return React.cloneElement(children, {
                ...mergedProps,
                ...eventHandlers,
            });
        }

        return <div {...itemProps}>{children}</div>;
    },
);

DropdownMenuItem.displayName = 'DropdownMenuItem';

interface DropdownMenuGroupProps extends React.HTMLAttributes<HTMLDivElement> {
    label?: string;
}

export const DropdownMenuGroup = React.forwardRef<HTMLDivElement, DropdownMenuGroupProps>(({ children, label, className, ...props }, ref) => {
    return (
        <div {...props} aria-label={label} className={`tiptap-button-group ${className || ''}`} ref={ref} role="group">
            {children}
        </div>
    );
});

DropdownMenuGroup.displayName = 'DropdownMenuGroup';

export const DropdownMenuSeparator = React.forwardRef<React.ElementRef<typeof Separator>, React.ComponentPropsWithoutRef<typeof Separator>>(
    ({ className, ...props }, ref) => <Separator className={`tiptap-dropdown-menu-separator ${className || ''}`} ref={ref} {...props} />,
);
DropdownMenuSeparator.displayName = Separator.displayName;
