import { cn } from '@/lib/cn';
import {
    useEffect,
    useLayoutEffect,
    useRef,
    useState,
    type ReactNode,
} from 'react';
import { createPortal } from 'react-dom';

type Align = 'start' | 'center' | 'end';

type TriggerProps = {
    open: boolean;
    toggle: () => void;
};

type PopoverProps = {
    trigger: (props: TriggerProps) => ReactNode;
    children: ReactNode;
    align?: Align;
};

type Pos = { top: number; left: number; transform: string };

const MARGIN = 6;
const VIEWPORT_PADDING = 8;

// Single open popover at a time across the whole app. Opening a second
// dropdown closes the prior one — the alternative (stacked menus) makes
// keyboard focus + outside-click handling ambiguous and looks like a bug.
let activeClose: (() => void) | null = null;

// Portaled popover. Anchored to the trigger's bounding rect via
// `position: fixed` so it escapes any overflow-hidden ancestor (Card with
// rounded corners, modals, table cells). Auto-flips upward when there isn't
// room below — keeps the panel visible for last-row dropdowns regardless of
// where the trigger sits in the layout.
export function Popover({ trigger, children, align = 'start' }: PopoverProps) {
    const [open, setOpen] = useState(false);
    const [pos, setPos] = useState<Pos | null>(null);
    const triggerRef = useRef<HTMLSpanElement>(null);
    const panelRef = useRef<HTMLDivElement>(null);

    const close = () => setOpen(false);
    const toggle = () => setOpen((v) => !v);

    // Register / unregister this popover as the singleton active one.
    // Opening evicts whichever other popover was open; closing clears the
    // slot so we don't keep a stale reference.
    useEffect(() => {
        if (!open) {
            if (activeClose === close) activeClose = null;
            return;
        }
        if (activeClose && activeClose !== close) {
            activeClose();
        }
        activeClose = close;
        return () => {
            if (activeClose === close) activeClose = null;
        };
    }, [open]);

    useLayoutEffect(() => {
        if (!open) {
            setPos(null);
            return;
        }
        const reposition = () => {
            const t = triggerRef.current;
            const p = panelRef.current;
            if (!t) return;
            const tr = t.getBoundingClientRect();
            const panelHeight = p?.offsetHeight ?? 0;
            const fitsBelow =
                tr.bottom + MARGIN + panelHeight + VIEWPORT_PADDING <=
                window.innerHeight;
            const top = fitsBelow
                ? tr.bottom + MARGIN
                : Math.max(VIEWPORT_PADDING, tr.top - MARGIN - panelHeight);

            // Compute the panel's final left edge directly (no transform) so we
            // can clamp it to the viewport — an `end`-aligned panel near the
            // right edge would otherwise spill off the LEFT on a phone.
            const panelWidth = p?.offsetWidth ?? 0;
            let left: number;
            if (align === 'start') {
                left = tr.left;
            } else if (align === 'center') {
                left = tr.left + tr.width / 2 - panelWidth / 2;
            } else {
                left = tr.right - panelWidth;
            }
            const maxLeft = window.innerWidth - panelWidth - VIEWPORT_PADDING;
            left = Math.min(
                Math.max(VIEWPORT_PADDING, left),
                Math.max(VIEWPORT_PADDING, maxLeft),
            );
            setPos({ top, left, transform: '' });
        };
        reposition();
        // Reposition on scroll/resize — using capture so nested scrollers count.
        window.addEventListener('scroll', reposition, true);
        window.addEventListener('resize', reposition);
        return () => {
            window.removeEventListener('scroll', reposition, true);
            window.removeEventListener('resize', reposition);
        };
    }, [open, align]);

    useEffect(() => {
        if (!open) return;
        const onDocMouseDown = (e: MouseEvent) => {
            const t = e.target as Node;
            if (
                triggerRef.current?.contains(t) ||
                panelRef.current?.contains(t)
            ) {
                return;
            }
            setOpen(false);
        };
        const onKey = (e: KeyboardEvent) => {
            if (e.key === 'Escape') setOpen(false);
        };
        document.addEventListener('mousedown', onDocMouseDown);
        document.addEventListener('keydown', onKey);
        return () => {
            document.removeEventListener('mousedown', onDocMouseDown);
            document.removeEventListener('keydown', onKey);
        };
    }, [open]);

    return (
        <>
            <span ref={triggerRef} className="inline-flex">
                {trigger({ open, toggle })}
            </span>
            {open &&
                typeof document !== 'undefined' &&
                createPortal(
                    <div
                        ref={panelRef}
                        role="dialog"
                        style={{
                            position: 'fixed',
                            top: pos?.top ?? 0,
                            left: pos?.left ?? 0,
                            transform: pos?.transform ?? '',
                            // Hide on the very first frame before measurement
                            // lands — avoids a flash at (0, 0).
                            visibility: pos ? 'visible' : 'hidden',
                        }}
                        className={cn(
                            // Opacity-only entrance — the panel's transform is owned by
                            // the positioning style above; an animated transform would
                            // fight it.
                            'animate-fade-in z-50 w-max rounded-md border border-border bg-card p-2 shadow-lg',
                        )}
                        onClick={() => setOpen(false)}
                    >
                        {children}
                    </div>,
                    document.body,
                )}
        </>
    );
}
