import { cn } from '@/lib/cn';
import { ChevronRight, Scissors } from 'lucide-react';
import { useEffect, useLayoutEffect, useRef, useState, type ComponentType } from 'react';
import { useEditor } from './store';

type Props = {
    x: number;
    y: number;
    onClose: () => void;
};

type IconType = ComponentType<{ className?: string }>;

type MenuItem =
    | {
          kind: 'item';
          label: string;
          shortcut?: string;
          disabled?: boolean;
          icon?: IconType;
          testId?: string;
          onClick: () => void;
      }
    | { kind: 'separator' }
    | { kind: 'submenu'; label: string; disabled?: boolean; items: MenuItem[] };

const MENU_W = 220;
const SUBMENU_W = 200;
const ITEM_H = 30;

export function ContextMenu({ x, y, onClose }: Props) {
    const selection = useEditor((s) => s.selection);
    const layers = useEditor((s) => s.layout.layers);
    const clipboardStyle = useEditor((s) => s.clipboardStyle);
    const clipboardLayers = useEditor((s) => s.clipboardLayers);
    const arrange = useEditor((s) => s.arrange);
    const alignSelected = useEditor((s) => s.alignSelected);
    const distributeSelected = useEditor((s) => s.distributeSelected);
    const groupSelected = useEditor((s) => s.groupSelected);
    const ungroupSelected = useEditor((s) => s.ungroupSelected);
    const duplicateSelected = useEditor((s) => s.duplicateSelected);
    const deleteSelected = useEditor((s) => s.deleteSelected);
    const updateLayer = useEditor((s) => s.updateLayer);
    const copyStyle = useEditor((s) => s.copyStyle);
    const pasteStyle = useEditor((s) => s.pasteStyle);
    const copyLayers = useEditor((s) => s.copyLayers);
    const pasteLayers = useEditor((s) => s.pasteLayers);
    const bindImageToShape = useEditor((s) => s.bindImageToShape);
    const unbindImageClip = useEditor((s) => s.unbindImageClip);

    const ref = useRef<HTMLDivElement>(null);
    const [pos, setPos] = useState({ x, y });

    useLayoutEffect(() => {
        const vw = window.innerWidth;
        const vh = window.innerHeight;
        const nextX = x + MENU_W > vw ? Math.max(0, x - MENU_W) : x;
        const nextY = y + ref.current!.offsetHeight > vh
            ? Math.max(0, y - ref.current!.offsetHeight)
            : y;
        setPos({ x: nextX, y: nextY });
    }, [x, y]);

    useEffect(() => {
        const onDown = (e: MouseEvent) => {
            if (ref.current && !ref.current.contains(e.target as Node)) onClose();
        };
        const onKey = (e: KeyboardEvent) => {
            if (e.key === 'Escape') onClose();
        };
        window.addEventListener('mousedown', onDown);
        window.addEventListener('keydown', onKey);
        return () => {
            window.removeEventListener('mousedown', onDown);
            window.removeEventListener('keydown', onKey);
        };
    }, [onClose]);

    const count = selection.length;
    const selectedLayers = layers.filter((l) => selection.includes(l.id));
    const hasGroup = selectedLayers.some((l) => l.type === 'group');
    const anyLocked = selectedLayers.some((l) => l.locked === true);
    const canPasteStyle = clipboardStyle !== null && count > 0;
    const canPasteLayers = clipboardLayers !== null && clipboardLayers.length > 0;

    // Clip-with-shape: exactly one image + one valid closed shape. Mirrors
    // the store's rejection rules (no line/arrow, no open path).
    let clipImageId: string | null = null;
    let clipShapeId: string | null = null;
    if (count === 2) {
        const [a, b] = selectedLayers;
        const imageCandidate =
            a && a.type === 'image' ? a : b && b.type === 'image' ? b : null;
        const shapeCandidate =
            a && a.type === 'shape' ? a : b && b.type === 'shape' ? b : null;
        if (imageCandidate && shapeCandidate && imageCandidate.id !== shapeCandidate.id) {
            const kind = shapeCandidate.shape;
            const isClippable =
                kind !== 'line' &&
                kind !== 'arrow' &&
                !(kind === 'path' && shapeCandidate.closed === false);
            if (isClippable) {
                clipImageId = imageCandidate.id;
                clipShapeId = shapeCandidate.id;
            }
        }
    }
    const canClipWithShape = clipImageId !== null && clipShapeId !== null;

    // Release-clip: exactly one image already bound to a shape.
    let releaseClipTargetId: string | null = null;
    if (count === 1) {
        const only = selectedLayers[0];
        if (only && only.type === 'image' && only.clipShapeId) {
            releaseClipTargetId = only.id;
        }
    }
    const canReleaseClip = releaseClipTargetId !== null;

    const toggleLock = () => {
        const target = !anyLocked;
        selection.forEach((id) => {
            updateLayer(id, (draft) => {
                draft.locked = target || undefined;
            });
        });
    };

    const run = (fn: () => void) => () => {
        fn();
        onClose();
    };

    const items: MenuItem[] = [
        {
            kind: 'submenu',
            label: 'Arrange',
            disabled: count === 0,
            items: [
                { kind: 'item', label: 'Bring to Front', onClick: run(() => arrange('front')) },
                { kind: 'item', label: 'Bring Forward', onClick: run(() => arrange('forward')) },
                { kind: 'item', label: 'Send Backward', onClick: run(() => arrange('backward')) },
                { kind: 'item', label: 'Send to Back', onClick: run(() => arrange('back')) },
            ],
        },
        {
            kind: 'submenu',
            label: 'Align',
            disabled: count < 2,
            items: [
                { kind: 'item', label: 'Left', onClick: run(() => alignSelected('left')) },
                { kind: 'item', label: 'Center horizontally', onClick: run(() => alignSelected('centerH')) },
                { kind: 'item', label: 'Right', onClick: run(() => alignSelected('right')) },
                { kind: 'item', label: 'Top', onClick: run(() => alignSelected('top')) },
                { kind: 'item', label: 'Center vertically', onClick: run(() => alignSelected('centerV')) },
                { kind: 'item', label: 'Bottom', onClick: run(() => alignSelected('bottom')) },
                { kind: 'separator' },
                {
                    kind: 'item',
                    label: 'Distribute horizontally',
                    disabled: count < 3,
                    onClick: run(() => distributeSelected('horizontal')),
                },
                {
                    kind: 'item',
                    label: 'Distribute vertically',
                    disabled: count < 3,
                    onClick: run(() => distributeSelected('vertical')),
                },
            ],
        },
        { kind: 'separator' },
        hasGroup
            ? { kind: 'item', label: 'Ungroup', shortcut: '⌘⇧G', onClick: run(ungroupSelected) }
            : {
                  kind: 'item',
                  label: 'Group',
                  shortcut: '⌘G',
                  disabled: count < 2,
                  onClick: run(groupSelected),
              },
        {
            kind: 'item',
            label: anyLocked ? 'Unlock' : 'Lock',
            disabled: count === 0,
            onClick: run(toggleLock),
        },
        { kind: 'separator' },
        {
            kind: 'item',
            label: 'Copy',
            shortcut: '⌘C',
            disabled: count === 0,
            onClick: run(copyLayers),
        },
        {
            kind: 'item',
            label: 'Paste',
            shortcut: '⌘V',
            disabled: !canPasteLayers,
            onClick: run(pasteLayers),
        },
        {
            kind: 'item',
            label: 'Duplicate',
            shortcut: '⌘D',
            disabled: count === 0,
            onClick: run(duplicateSelected),
        },
        ...(canClipWithShape
            ? ([
                  { kind: 'separator' },
                  {
                      kind: 'item',
                      label: 'Clip with shape',
                      icon: Scissors,
                      testId: 'ctxmenu-clip-with-shape',
                      onClick: run(() => {
                          if (clipImageId && clipShapeId) {
                              bindImageToShape(clipImageId, clipShapeId);
                          }
                      }),
                  },
              ] satisfies MenuItem[])
            : []),
        ...(canReleaseClip
            ? ([
                  { kind: 'separator' },
                  {
                      kind: 'item',
                      label: 'Release clip',
                      icon: Scissors,
                      testId: 'ctxmenu-release-clip',
                      onClick: run(() => {
                          if (releaseClipTargetId) {
                              unbindImageClip(releaseClipTargetId);
                          }
                      }),
                  },
              ] satisfies MenuItem[])
            : []),
        {
            kind: 'item',
            label: 'Delete',
            shortcut: 'Del',
            disabled: count === 0,
            onClick: run(deleteSelected),
        },
        { kind: 'separator' },
        {
            kind: 'item',
            label: 'Copy style',
            shortcut: '⌘⌥C',
            disabled: count === 0,
            onClick: run(copyStyle),
        },
        {
            kind: 'item',
            label: 'Paste style',
            shortcut: '⌘⌥V',
            disabled: !canPasteStyle,
            onClick: run(pasteStyle),
        },
    ];

    return (
        <div
            ref={ref}
            role="menu"
            className="fixed z-[100] min-w-[220px] rounded-md border border-border bg-card py-1 text-[13px] text-foreground shadow-lg"
            style={{ left: pos.x, top: pos.y }}
            onContextMenu={(e) => e.preventDefault()}
        >
            {items.map((item, i) => (
                <MenuRow key={i} item={item} />
            ))}
        </div>
    );
}

function MenuRow({ item }: { item: MenuItem }) {
    const [open, setOpen] = useState(false);
    const rowRef = useRef<HTMLDivElement>(null);

    if (item.kind === 'separator') {
        return <div className="my-1 h-px bg-border" />;
    }

    if (item.kind === 'submenu') {
        const rect = rowRef.current?.getBoundingClientRect();
        const flipLeft =
            rect && rect.right + SUBMENU_W > window.innerWidth;
        return (
            <div
                ref={rowRef}
                className={cn(
                    'relative flex items-center justify-between px-3 py-1.5 transition-colors',
                    item.disabled
                        ? 'cursor-default text-muted-foreground/50'
                        : 'cursor-default hover:bg-accent/10',
                )}
                onMouseEnter={() => !item.disabled && setOpen(true)}
                onMouseLeave={() => setOpen(false)}
            >
                <span>{item.label}</span>
                <ChevronRight className="h-3.5 w-3.5 text-muted-foreground" />
                {open && !item.disabled && (
                    <div
                        className="absolute top-0 z-[101] min-w-[200px] rounded-md border border-border bg-card py-1 shadow-lg"
                        style={flipLeft ? { right: '100%' } : { left: '100%' }}
                    >
                        {item.items.map((sub, i) => (
                            <MenuRow key={i} item={sub} />
                        ))}
                    </div>
                )}
            </div>
        );
    }

    const Icon = item.icon;
    return (
        <button
            type="button"
            disabled={item.disabled}
            onClick={item.onClick}
            data-testid={item.testId}
            className={cn(
                'flex w-full items-center justify-between px-3 py-1.5 text-left transition-colors',
                item.disabled
                    ? 'cursor-default text-muted-foreground/50'
                    : 'hover:bg-accent/10',
            )}
            style={{ minHeight: ITEM_H }}
        >
            <span className="flex items-center gap-2">
                {Icon && <Icon className="h-3.5 w-3.5 text-muted-foreground" />}
                <span>{item.label}</span>
            </span>
            {item.shortcut && (
                <span className="ml-4 text-[11px] text-muted-foreground">{item.shortcut}</span>
            )}
        </button>
    );
}
