/**
 * Right-click menu for the canvas — the Figma one, with the entries this editor
 * can actually perform.
 *
 * Hand-rolled rather than Radix: every menu in the editor is a dropdown anchored
 * to a button, and a context menu is anchored to a POINT that may sit anywhere,
 * including over the canvas's own pointer handlers. A plain fixed-position list
 * with an outside-click / Escape / scroll close is less code than teaching a
 * dropdown to behave, and it keeps the dark toolbar styling in one place.
 *
 * The menu never invents behaviour: each row calls the same store action the
 * keyboard shortcut and the panels call, so there is one implementation of
 * "bring to front" and one of "group", not three.
 */
import { useEffect, useLayoutEffect, useRef, useState } from 'react';

const IS_MAC = typeof navigator !== 'undefined'
    && /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent || '');

/**
 * Modifiers, written the way the reader's own keyboard writes them.
 *
 * ⌘ ⌥ ⌃ ⇧ are Mac keycaps and mean nothing to anyone else — an admin on Windows
 * looking at "⌥ Ctrl L" has no way to know that ⌥ is their Alt key. So the
 * symbols survive only on a Mac, and everywhere else each one is spelled out.
 * The shortcut strings stay written in symbols (with `$mod` for the platform's
 * command key); this is the one place that decides how they are shown.
 */
const KEYCAPS = IS_MAC
    ? { $mod: '⌘', '⌥': '⌥', '⌃': '⌃', '⇧': '⇧' }
    : { $mod: 'Ctrl', '⌥': 'Alt', '⌃': 'Ctrl', '⇧': 'Shift' };

/**
 * Modifiers are also listed in the reader's own ORDER — ⌥⌘L on a Mac, Ctrl Alt L
 * on Windows — so a shortcut string can be written whichever way round and still
 * come out reading like the platform's own documentation.
 */
const MOD_ORDER = IS_MAC ? ['⌃', '⌥', '⇧', '$mod'] : ['$mod', '⌃', '⌥', '⇧'];

function showShortcut(shortcut) {
    const parts = String(shortcut).trim().split(/\s+/);
    const mods = parts.filter((p) => p in KEYCAPS);
    const rest = parts.filter((p) => !(p in KEYCAPS));
    const caps = mods
        .sort((a, b) => MOD_ORDER.indexOf(a) - MOD_ORDER.indexOf(b))
        .map((m) => KEYCAPS[m]);
    // ⌃ and $mod are both "Ctrl" off a Mac; a chip must not read "Ctrl Ctrl M".
    return [...new Set(caps), ...rest].join(' ');
}

const rowCls = `w-full flex items-center gap-2 px-2.5 py-1.5 text-[11px] text-left rounded-[5px]
    text-white/90 hover:bg-[#0d99ff] hover:text-white
    disabled:opacity-35 disabled:hover:bg-transparent disabled:hover:text-white/90 disabled:cursor-default`;

/**
 * @param positioning  'absolute' anchors the menu inside its offset parent — what
 *   the canvas wants, so the editor can sit in any layout (or an embed) and still
 *   put the menu on the pointer. 'fixed' anchors it to the viewport instead, for
 *   callers whose host is a SCROLLING, clipping box: a menu absolutely placed
 *   inside the layers panel would be cut off at the panel's edge.
 */
export default function CanvasContextMenu({ at, items, onClose, positioning = 'absolute' }) {
    const ref = useRef(null);
    const [pos, setPos] = useState({ left: at?.x ?? 0, top: at?.y ?? 0 });

    // Keep the menu on screen: flip it back inside whichever edge it would cross.
    // `at` is in client coordinates; the offset parent converts them when the menu
    // is placed absolutely.
    useLayoutEffect(() => {
        const node = ref.current;
        if (!node || !at) return;
        const { width, height } = node.getBoundingClientRect();
        const host = positioning === 'fixed' ? null : node.offsetParent?.getBoundingClientRect();
        const pad = 8;
        const left = Math.max(pad, Math.min(at.x, window.innerWidth - width - pad));
        const top = Math.max(pad, Math.min(at.y, window.innerHeight - height - pad));
        setPos({ left: left - (host?.left || 0), top: top - (host?.top || 0) });
    }, [at, positioning]);

    useEffect(() => {
        if (!at) return undefined;
        const onKey = (e) => {
            if (e.key === 'Escape') { e.stopPropagation(); onClose(); }
        };
        const onDown = (e) => {
            if (!ref.current?.contains(e.target)) onClose();
        };
        // Capture so the canvas's own pointer handlers don't act on the click
        // that is only meant to dismiss the menu.
        window.addEventListener('pointerdown', onDown, true);
        window.addEventListener('keydown', onKey, true);
        window.addEventListener('wheel', onClose, { passive: true });
        window.addEventListener('blur', onClose);
        return () => {
            window.removeEventListener('pointerdown', onDown, true);
            window.removeEventListener('keydown', onKey, true);
            window.removeEventListener('wheel', onClose);
            window.removeEventListener('blur', onClose);
        };
    }, [at, onClose]);

    if (!at) return null;

    // Rendered where it is called from rather than portaled to <body>: the menu
    // belongs to the editor's own stacking context and theme, and `fixed` already
    // places it against the viewport.
    return (
        <div
            ref={ref}
            role="menu"
            data-export-ignore="1"
            className={`${positioning === 'fixed' ? 'fixed' : 'absolute'} z-[60] min-w-[204px] py-1 rounded-[10px] bg-[#1e1e1e]
                shadow-[0_8px_28px_rgba(0,0,0,0.45),0_0_0_0.5px_rgba(255,255,255,0.08)]`}
            style={{ left: pos.left, top: pos.top }}
            onContextMenu={(e) => e.preventDefault()}
        >
            {items.map((item, i) => {
                if (item.separator) {
                    // eslint-disable-next-line react/no-array-index-key
                    return <div key={`sep-${i}`} className="my-1 h-px bg-white/10" />;
                }
                if (item.header) {
                    return (
                        <div
                            // eslint-disable-next-line react/no-array-index-key
                            key={`head-${i}`}
                            className="px-3.5 pt-1 pb-0.5 text-[9px] font-semibold uppercase tracking-wide text-white/35"
                        >
                            {item.label}
                        </div>
                    );
                }
                const Icon = item.icon;
                return (
                    <div key={item.label} className="px-1">
                        <button
                            type="button"
                            role="menuitem"
                            disabled={item.disabled}
                            className={rowCls}
                            onClick={() => {
                                if (item.disabled) return;
                                onClose();
                                item.onSelect?.();
                            }}
                        >
                            <span className="w-3.5 shrink-0 flex items-center justify-center">
                                {Icon ? <Icon size={13} stroke={1.75} /> : null}
                            </span>
                            <span className="flex-1 truncate">{item.label}</span>
                            {item.shortcut && (
                                <span className="shrink-0 text-[10px] text-white/40">
                                    {showShortcut(item.shortcut)}
                                </span>
                            )}
                        </button>
                    </div>
                );
            })}
        </div>
    );
}
