import { useRef, useState } from 'react';
import * as stylex from '@stylexjs/stylex';

import type { DragEvent, PointerEvent, ReactNode } from 'react';

import { DRAG_HANDLE_SELECTOR } from './constants';
import { draggableStyles } from './styles';

interface DraggableProps {
  children: ReactNode;
  index: number;
  dragIndex: number | null;
  onDragStart?: (index: number) => void;
  onDragOver: (e: DragEvent, index: number) => void;
  onDragEnd?: () => void;
}

export const Draggable = ({
  children,
  index,
  dragIndex,
  onDragStart,
  onDragOver,
  onDragEnd,
}: DraggableProps) => {
  const ref = useRef<HTMLDivElement>(null);
  const [draggable, setDraggable] = useState(false);
  const isDragging = dragIndex === index;

  // Native drag fires `dragstart` on the draggable container, so the event
  // target can't tell us where the drag originated. Instead we only enable
  // `draggable` while the pointer is pressed inside the drag handle.
  const onPointerDown = (e: PointerEvent) => {
    const handle = ref.current?.querySelector(DRAG_HANDLE_SELECTOR);
    setDraggable(Boolean(handle?.contains(e.target as Node)));
  };

  return (
    <div
      {...stylex.props(
        draggableStyles.draggable,
        isDragging && draggableStyles.dragging
      )}
      ref={ref}
      draggable={draggable}
      onPointerDown={onPointerDown}
      onDragStart={(e) => {
        onDragStart?.(index);
        e.dataTransfer.effectAllowed = 'move';
      }}
      onDragOver={(e) => {
        e.dataTransfer.dropEffect = 'move';
        onDragOver(e, index);
      }}
      onDragEnd={() => {
        setDraggable(false);
        onDragEnd?.();
      }}
    >
      {children}
    </div>
  );
};
