import { useCallback, useRef, useMemo } from "react";
import getStyle from "./get-style";
import useDraggablePublisher from "../use-draggable-publisher/use-draggable-publisher";
import AppContext from "../context/app-context";
import DroppableContext from "../context/droppable-context";
import { useClonePropValidation, useValidation } from "./use-validation";
import useRequiredContext from "../use-required-context";

function preventHtml5Dnd(event) {
  event.preventDefault();
}

export default function Draggable(props) {
  // reference to DOM node
  const ref = useRef(null);
  const setRef = useCallback((el) => {
    ref.current = el;
  }, []);
  const getRef = useCallback(() => ref.current, []);

  // context
  const { contextId, dragHandleUsageInstructionsId, registry } = useRequiredContext(AppContext);
  const { type, droppableId } = useRequiredContext(DroppableContext);
  const descriptor = useMemo(
    () => ({
      id: props.draggableId,
      index: props.index,
      type,
      droppableId
    }),
    [props.draggableId, props.index, type, droppableId]
  );

  // props
  const {
    // ownProps
    children,
    draggableId,
    isEnabled,
    shouldRespectForcePress,
    canDragInteractiveElements,
    isClone,
    // mapProps
    mapped,
    // dispatchProps
    dropAnimationFinished: dropAnimationFinishedAction
  } = props;

  // Validating props and innerRef
  useValidation(props, contextId, getRef);

  // Clones do not speak to the dimension marshal
  // We are violating the rules of hooks here: conditional hooks.
  // In this specific use case it is okay as an item will always either be a
  // clone or not for it's whole lifecycle

  // Being super sure that isClone is not changing during a draggable lifecycle
  useClonePropValidation(isClone);
  if (!isClone) {
    const forPublisher = useMemo(
      () => ({
        descriptor,
        registry,
        getDraggableRef: getRef,
        canDragInteractiveElements,
        shouldRespectForcePress,
        isEnabled
      }),
      [descriptor, registry, getRef, canDragInteractiveElements, shouldRespectForcePress, isEnabled]
    );
    useDraggablePublisher(forPublisher);
  }

  const dragHandleProps = useMemo(
    () =>
      isEnabled
        ? {
            // See `draggable-types` for an explanation of why these are used
            tabIndex: 0,
            role: "button",
            "aria-describedby": dragHandleUsageInstructionsId,
            "data-rbd-drag-handle-draggable-id": draggableId,
            "data-rbd-drag-handle-context-id": contextId,
            draggable: false,
            onDragStart: preventHtml5Dnd
          }
        : null,
    [contextId, dragHandleUsageInstructionsId, draggableId, isEnabled]
  );
  const onMoveEnd = useCallback(
    (event) => {
      if (mapped.type !== "DRAGGING") {
        return;
      }
      if (!mapped.dropping) {
        return;
      }

      // There might be other properties on the element that are
      // being transitioned. We do not want those to end a drop animation!
      if (event.propertyName !== "transform") {
        return;
      }
      dropAnimationFinishedAction();
    },
    [dropAnimationFinishedAction, mapped]
  );
  const provided = useMemo(() => {
    const style = getStyle(mapped);
    const onTransitionEnd = mapped.type === "DRAGGING" && mapped.dropping ? onMoveEnd : null;
    const result = {
      innerRef: setRef,
      draggableProps: {
        "data-rbd-draggable-context-id": contextId,
        "data-rbd-draggable-id": draggableId,
        style,
        onTransitionEnd
      },
      dragHandleProps
    };
    return result;
  }, [contextId, dragHandleProps, draggableId, mapped, onMoveEnd, setRef]);
  const rubric = useMemo(
    () => ({
      draggableId: descriptor.id,
      type: descriptor.type,
      source: {
        index: descriptor.index,
        droppableId: descriptor.droppableId
      }
    }),
    [descriptor.droppableId, descriptor.id, descriptor.index, descriptor.type]
  );
  return children(provided, mapped.snapshot, rubric);
}
