// Code from ally.js

/*
  Observe keyboard-, pointer-, mouse- and touch-events so that a query for
  the current interaction type can be made at any time. For pointer interaction
  this observer is limited to pointer button down/up - move is not observed!

  USAGE:
    var listener = engage();
    listener.get() === {pointer: Boolean, key: Boolean}
*/

// counters to track primary input
let _activePointers = 0;
let _activeKeys = 0;

const pointerStartEvents = [
  'touchstart',
  'pointerdown',
  'MSPointerDown',
  'mousedown',
];
const pointerEndEvents = [
  'touchend',
  'touchcancel',
  'pointerup',
  'MSPointerUp',
  'pointercancel',
  'MSPointerCancel',
  'mouseup',
];

function handleWindowBlurEvent() {
  // reset internal counters when window loses focus
  _activePointers = 0;
  _activeKeys = 0;
}

function handlePointerStartEvent(event: PointerEvent) {
  if (event.isPrimary === false) {
    // ignore non-primary pointer events
    // https://w3c.github.io/pointerevents/#widl-PointerEvent-isPrimary
    return;
  }

  // mousedown without following mouseup
  // (likely not possible in Chrome)
  _activePointers += 1;
}

function handlePointerEndEvent(event: any) {
  if (event.isPrimary === false) {
    // ignore non-primary pointer events
    // https://w3c.github.io/pointerevents/#widl-PointerEvent-isPrimary
    return;
  } else if (event.touches) {
    _activePointers = event.touches.length;
    return;
  }

  // delay reset to when the current handlers are executed
  ((window as any).setImmediate || window.setTimeout)(() => {
    // mouseup without prior mousedown
    // (drag something out of the window)
    _activePointers = Math.max(_activePointers - 1, 0);
  });
}

function handleKeyStartEvent(event: KeyboardEvent) {
  // ignore modifier keys
  switch (event.keyCode || event.which) {
    case 16: // space
    case 17: // control
    case 18: // alt
    case 91: // command left
    case 93: // command right
      return;
    default:
      break;
  }

  // keydown without a following keyup
  // (may happen on CMD+TAB)
  _activeKeys += 1;
}

function handleKeyEndEvent(event: KeyboardEvent) {
  // ignore modifier keys
  switch (event.keyCode || event.which) {
    case 16: // space
    case 17: // control
    case 18: // alt
    case 91: // command left
    case 93: // command right
      return;
    default:
      break;
  }

  // delay reset to when the current handlers are executed
  ((window as any).setImmediate || window.setTimeout)(() => {
    // keyup without prior keydown
    // (may happen on CMD+R)
    _activeKeys = Math.max(_activeKeys - 1, 0);
  });
}

function getInteractionType() {
  return {
    pointer: Boolean(_activePointers),
    key: Boolean(_activeKeys),
  };
}

function disengage() {
  _activePointers = _activeKeys = 0;
  window.removeEventListener('blur', handleWindowBlurEvent, false);
  document.documentElement.removeEventListener(
    'keydown',
    handleKeyStartEvent,
    true,
  );
  document.documentElement.removeEventListener(
    'keyup',
    handleKeyEndEvent,
    true,
  );
  pointerStartEvents.forEach(event => {
    document.documentElement.removeEventListener(
      event,
      handlePointerStartEvent,
      true,
    );
  });
  pointerEndEvents.forEach(event => {
    document.documentElement.removeEventListener(
      event,
      handlePointerEndEvent,
      true,
    );
  });
}

function engage() {
  // window blur must be in bubble phase so it won't capture regular blurs
  window.addEventListener('blur', handleWindowBlurEvent, false);
  // handlers to identify the method of focus change
  document.documentElement.addEventListener(
    'keydown',
    handleKeyStartEvent,
    true,
  );
  document.documentElement.addEventListener('keyup', handleKeyEndEvent, true);
  pointerStartEvents.forEach(event => {
    document.documentElement.addEventListener(
      event,
      handlePointerStartEvent,
      true,
    );
  });
  pointerEndEvents.forEach(event => {
    document.documentElement.addEventListener(
      event,
      handlePointerEndEvent,
      true,
    );
  });

  return {
    get: getInteractionType,
  };
}

export default { engage, disengage };
