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

type Side =
    | 'top'
    | 'bottom'
    | 'left'
    | 'right'
    | 'bottom-start'
    | 'bottom-end'
    | 'top-start'
    | 'top-end';

type TooltipProps = {
    label: ReactNode;
    shortcut?: string;
    side?: Side;
    delay?: number;
    /**
     * Opt-in for explainer-style tooltips (full sentences). Flips
     * `whitespace-nowrap` → `whitespace-normal` and caps width so the label
     * wraps instead of stretching the viewport. Also portals the tooltip
     * into `document.body` via `position: fixed`, so the wider tooltip
     * escapes any ancestor `overflow:auto/hidden` clipping (e.g. when the
     * trigger sits inside a scrolling sidebar). Tight icon tooltips stay on
     * the simpler absolute path.
     */
    multiline?: boolean;
    children: ReactNode;
};

const sideClasses: Record<Side, string> = {
    top: 'bottom-full left-1/2 -translate-x-1/2 mb-1.5',
    bottom: 'top-full left-1/2 -translate-x-1/2 mt-1.5',
    left: 'right-full top-1/2 -translate-y-1/2 mr-1.5',
    right: 'left-full top-1/2 -translate-y-1/2 ml-1.5',
    'bottom-start': 'top-full left-0 mt-1.5',
    'bottom-end': 'top-full right-0 mt-1.5',
    'top-start': 'bottom-full left-0 mb-1.5',
    'top-end': 'bottom-full right-0 mb-1.5',
};

const GAP = 6;

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

function computeFixedPosition(rect: DOMRect, side: Side): FixedPosition {
    let top = 0;
    let left = 0;
    let tx = '0';
    let ty = '0';

    if (side === 'top' || side === 'top-start' || side === 'top-end') {
        top = rect.top - GAP;
        ty = '-100%';
        if (side === 'top') {
            left = rect.left + rect.width / 2;
            tx = '-50%';
        } else if (side === 'top-start') {
            left = rect.left;
            tx = '0';
        } else {
            left = rect.right;
            tx = '-100%';
        }
    } else if (side === 'bottom' || side === 'bottom-start' || side === 'bottom-end') {
        top = rect.bottom + GAP;
        ty = '0';
        if (side === 'bottom') {
            left = rect.left + rect.width / 2;
            tx = '-50%';
        } else if (side === 'bottom-start') {
            left = rect.left;
            tx = '0';
        } else {
            left = rect.right;
            tx = '-100%';
        }
    } else if (side === 'left') {
        left = rect.left - GAP;
        tx = '-100%';
        top = rect.top + rect.height / 2;
        ty = '-50%';
    } else {
        left = rect.right + GAP;
        tx = '0';
        top = rect.top + rect.height / 2;
        ty = '-50%';
    }

    return { top, left, transform: `translate(${tx}, ${ty})` };
}

export function Tooltip({ label, shortcut, side = 'bottom', delay = 300, multiline = false, children }: TooltipProps) {
    const triggerRef = useRef<HTMLSpanElement>(null);
    const [open, setOpen] = useState(false);
    const [fixedPos, setFixedPos] = useState<FixedPosition | null>(null);
    const timerRef = useRef<number | null>(null);

    const clearTimer = () => {
        if (timerRef.current !== null) {
            window.clearTimeout(timerRef.current);
            timerRef.current = null;
        }
    };

    const show = () => {
        clearTimer();
        timerRef.current = window.setTimeout(() => {
            if (multiline && triggerRef.current) {
                const rect = triggerRef.current.getBoundingClientRect();
                setFixedPos(computeFixedPosition(rect, side));
            }
            setOpen(true);
        }, delay);
    };

    const hide = () => {
        clearTimer();
        setOpen(false);
        setFixedPos(null);
    };

    // Portal tooltips don't track the trigger as the user scrolls. Hiding on
    // scroll is the cheapest correctness fix — re-showing on next hover is
    // fast enough.
    useEffect(() => {
        if (!open || !multiline) return;
        const onScroll = () => hide();
        window.addEventListener('scroll', onScroll, true);
        window.addEventListener('resize', onScroll);
        return () => {
            window.removeEventListener('scroll', onScroll, true);
            window.removeEventListener('resize', onScroll);
        };
    }, [open, multiline]);

    const tooltipContent = (
        <>
            {label}
            {shortcut && (
                <kbd className="rounded border border-background/20 bg-background/10 px-1 py-px font-mono text-[10px] leading-none text-background/80">
                    {shortcut}
                </kbd>
            )}
        </>
    );

    return (
        <span
            ref={triggerRef}
            className="relative inline-flex"
            onMouseEnter={show}
            onMouseLeave={hide}
            onFocus={show}
            onBlur={hide}
        >
            {children}
            {open && !multiline && (
                <span
                    role="tooltip"
                    className={cn(
                        'pointer-events-none absolute z-50 inline-flex items-center gap-1.5 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-[11px] font-medium leading-snug text-background shadow-sm',
                        sideClasses[side],
                    )}
                >
                    {tooltipContent}
                </span>
            )}
            {open && multiline && fixedPos &&
                createPortal(
                    <span
                        role="tooltip"
                        style={{
                            position: 'fixed',
                            top: fixedPos.top,
                            left: fixedPos.left,
                            transform: fixedPos.transform,
                            zIndex: 70,
                        }}
                        className="pointer-events-none inline-flex w-max max-w-[240px] items-center gap-1.5 whitespace-normal rounded-md bg-foreground px-2 py-1 text-left text-[11px] font-medium leading-snug text-background shadow-sm"
                    >
                        {tooltipContent}
                    </span>,
                    document.body,
                )}
        </span>
    );
}
