import { useCallback, useMemo, useRef } from "react";
import { invariant } from "../../../invariant";
import * as keyCodes from "../../key-codes";
import bindEvents from "../../event-bindings/bind-events";
import preventStandardKeyEvents from "./util/prevent-standard-key-events";
import supportedPageVisibilityEventName from "./util/supported-page-visibility-event-name";
import useLayoutEffect from "../../use-isomorphic-layout-effect";

function noop() {}

const scrollJumpKeys = {
  [keyCodes.pageDown]: true,
  [keyCodes.pageUp]: true,
  [keyCodes.home]: true,
  [keyCodes.end]: true
};

function getDraggingBindings(actions, stop) {
  function cancel() {
    stop();
    actions.cancel();
  }

  function drop() {
    stop();
    actions.drop();
  }

  return [
    {
      eventName: "keydown",
      fn: (event) => {
        if (event.keyCode === keyCodes.escape) {
          event.preventDefault();
          cancel();
          return;
        }

        // Dropping
        if (event.keyCode === keyCodes.space) {
          // need to stop parent Draggable's thinking this is a lift
          event.preventDefault();
          drop();
          return;
        }

        // Movement

        if (event.keyCode === keyCodes.arrowDown) {
          event.preventDefault();
          actions.moveDown();
          return;
        }
        if (event.keyCode === keyCodes.arrowUp) {
          event.preventDefault();
          actions.moveUp();
          return;
        }
        if (event.keyCode === keyCodes.arrowRight) {
          event.preventDefault();
          actions.moveRight();
          return;
        }
        if (event.keyCode === keyCodes.arrowLeft) {
          event.preventDefault();
          actions.moveLeft();
          return;
        }

        // preventing scroll jumping at this time
        if (scrollJumpKeys[event.keyCode]) {
          event.preventDefault();
          return;
        }
        preventStandardKeyEvents(event);
      }
    },
    // any mouse actions kills a drag
    {
      eventName: "mousedown",
      fn: cancel
    },
    {
      eventName: "mouseup",
      fn: cancel
    },
    {
      eventName: "click",
      fn: cancel
    },
    {
      eventName: "touchstart",
      fn: cancel
    },
    // resizing the browser kills a drag
    {
      eventName: "resize",
      fn: cancel
    },
    // kill if the user is using the mouse wheel
    // We are not supporting wheel / trackpad scrolling with keyboard dragging
    {
      eventName: "wheel",
      fn: cancel,
      // chrome says it is a violation for this to not be passive
      // it is fine for it to be passive as we just cancel as soon as we get
      // any event
      options: {
        passive: true
      }
    },
    // Cancel on page visibility change
    {
      eventName: supportedPageVisibilityEventName,
      fn: cancel
    }
  ];
}

export default function useKeyboardSensor(api) {
  const unbindEventsRef = useRef(noop);
  const startCaptureBinding = useMemo(
    () => ({
      eventName: "keydown",
      fn: function onKeyDown(event) {
        // Event already used
        if (event.defaultPrevented) {
          return;
        }

        // Need to start drag with a spacebar press
        if (event.keyCode !== keyCodes.space) {
          return;
        }
        const draggableId = api.findClosestDraggableId(event);
        if (!draggableId) {
          return;
        }
        const preDrag = api.tryGetLock(
          draggableId,
          // abort function not defined yet
           
          stop,
          {
            sourceEvent: event
          }
        );

        // Cannot start capturing at this time
        if (!preDrag) {
          return;
        }

        // we are consuming the event
        event.preventDefault();
        let isCapturing = true;

        // There is no pending period for a keyboard drag
        // We can lift immediately
        const actions = preDrag.snapLift();

        // unbind this listener
        unbindEventsRef.current();

        // setup our function to end everything
        function stop() {
          invariant(isCapturing, "Cannot stop capturing a keyboard drag when not capturing");
          isCapturing = false;

          // unbind dragging bindings
          unbindEventsRef.current();
          // start listening for capture again
           
          listenForCapture();
        }

        // bind dragging listeners
        unbindEventsRef.current = bindEvents(window, getDraggingBindings(actions, stop), {
          capture: true,
          passive: false
        });
      }
    }),
    // not including startPendingDrag as it is not defined initially
    [api]
  );
  const listenForCapture = useCallback(
    function tryStartCapture() {
      const options = {
        passive: false,
        capture: true
      };
      unbindEventsRef.current = bindEvents(window, [startCaptureBinding], options);
    },
    [startCaptureBinding]
  );
  useLayoutEffect(
    function mount() {
      listenForCapture();

      // kill any pending window events when unmounting
      return function unmount() {
        unbindEventsRef.current();
      };
    },
    [listenForCapture]
  );
}
