/**
 * @module tgam-patterns-scroll-menu
 */

import debounce from "lodash/debounce";
import EVENT_NAMES from "./event-names";
import ee from "./event-emitter";

const moveStartSpeed = 2;
const speedMultiplier = 1.11;
const maxMoveSpeed = 20;
const intervalTime = 50;
const menuJumpValue = 100;
const clickThreshold = 200;
const arrowLeftSelector = ".c-control--arrow--left";
const arrowRightSelector = ".c-control--arrow--right";
const menuListSelector = ".c-scroll-menu__list";
const navSelector = ".c-scroll-menu__nav";
const scrollMenuDisabledClass = "c-scroll-menu--disabled";
const arrowDisabledClass = "c-control--arrow--disabled";

/**
 * @method init
 * @description init method, adds event listener for scroll and resize
 * @param {HTMLElement} rootElement - an element which will be the scope of the querySelector method
 * @return {undefined}
 */
function init(rootElement) {
  this.rootElement = rootElement;
  this.menuList = rootElement.querySelector(menuListSelector);
  this.navContainer = rootElement.querySelector(navSelector);
  this.arrowLeft = rootElement.querySelector(arrowLeftSelector);
  this.arrowRight = rootElement.querySelector(arrowRightSelector);
  this.moveSpeed = moveStartSpeed;

  // scrolling menu event handlers
  if (this.menuList && this.arrowLeft && this.arrowRight) {
    // left arrow click
    this.arrowLeft.addEventListener("mousedown", this.onLeftArrowTouch.bind(this));
    this.arrowLeft.addEventListener("touchstart", this.onLeftArrowTouch.bind(this));

    // left arrow release
    this.arrowLeft.addEventListener("mouseup", this.onLeftArrowRelease.bind(this));
    this.arrowLeft.addEventListener("mouseleave", this.onLeftArrowRelease.bind(this));
    this.arrowLeft.addEventListener("touchend", this.onLeftArrowRelease.bind(this));
    this.arrowLeft.addEventListener("touchcancel", this.onLeftArrowRelease.bind(this));

    // right arrow click
    this.arrowRight.addEventListener("mousedown", this.onRightArrowTouch.bind(this));
    this.arrowRight.addEventListener("touchstart", this.onRightArrowTouch.bind(this));

    // right arrow release
    this.arrowRight.addEventListener("mouseup", this.onRightArrowRelease.bind(this));
    this.arrowRight.addEventListener("mouseleave", this.onRightArrowRelease.bind(this));
    this.arrowRight.addEventListener("touchend", this.onRightArrowRelease.bind(this));
    this.arrowRight.addEventListener("touchcancel", this.onRightArrowRelease.bind(this));

    this.menuList.addEventListener("scroll", debounce(this.onContentTouchStart.bind(this)), 100);
    this.menuList.addEventListener("touchmove", debounce(this.onContentTouchStart.bind(this)), 100);
    this.menuList.addEventListener("touchend", this.onContentTouchEnd.bind(this));
    this.menuList.addEventListener("touchcancel", this.onContentTouchEnd.bind(this));
  }

  this.needsArrows();
  this.checkScrollPosition();

  ee.on(EVENT_NAMES.scrollMenu.touchMoveStart, () => {
    this.checkScrollPosition();
  });
}

//////////////////////
// LEFT
//////////////////////

/**
 * @method onLeftArrowTouch
 * @description TODO
 * @return {undefined}
 */
function onLeftArrowTouch() {
  this.cancelInterval();
  this.mouseLongPress = false;
  this.mouseIsDown = true;
  this.clickTimeout = setTimeout(leftClickTimeout.bind(this), clickThreshold);
}

/**
 * @method onLeftArrowRelease
 * @description TODO
 * @return {undefined}
 */
function onLeftArrowRelease() {
  this.cancelInterval();
  this.cancelClickTimeout();

  if (this.mouseIsDown && !this.mouseLongPress) {
    moveMenuLeft.call(this);
    this.mouseLongPress = false;
  }

  this.mouseIsDown = false;
}

/**
 * @method leftClickTimeout
 * @description TODO
 * @return {undefined}
 */
function leftClickTimeout() {
  cancelClickTimeout.call(this);
  this.mouseLongPress = true;
  this.scrollInterval = setInterval(scrollMenuLeft.bind(this), intervalTime);
}

//////////////////////
// RIGHT
//////////////////////

/**
 * @method onRightArrowTouch
 * @description TODO
 * @return {undefined}
 */
function onRightArrowTouch() {
  cancelInterval.call(this);
  this.mouseLongPress = false;
  this.mouseIsDown = true;
  this.clickTimeout = setTimeout(rightClickTimeout.bind(this), clickThreshold);
}

/**
 * @method onRightArrowRelease
 * @description TODO
 * @return {undefined}
 */
function onRightArrowRelease() {
  cancelInterval.call(this);
  cancelClickTimeout.call(this);

  if (this.mouseIsDown && !this.mouseLongPress) {
    moveMenuRight.call(this);
    this.mouseLongPress = false;
  }

  this.mouseIsDown = false;
}

/**
 * @method rightClickTimeout
 * @description TODO
 * @return {undefined}
 */
function rightClickTimeout() {
  cancelClickTimeout.call(this);
  this.mouseLongPress = true;
  this.scrollInterval = setInterval(scrollMenuRight.bind(this), intervalTime);
}

//////////////////////

/**
 * @method moveMenuLeft
 * @description TODO
 * @return {undefined}
 */
function moveMenuLeft() {
  this.menuList.scrollLeft -= menuJumpValue;
  this.checkScrollPosition();
}

/**
 * @method moveMenuRight
 * @description TODO
 * @return {undefined}
 */
function moveMenuRight() {
  this.menuList.scrollLeft += menuJumpValue;
  this.checkScrollPosition();
}

/**
 * @method scrollMenuLeft
 * @description the speed increases the longer you hold the button
 * @return {undefined}
 */
function scrollMenuLeft() {
  this.moveSpeed *= speedMultiplier;
  this.moveSpeed > maxMoveSpeed && (this.moveSpeed = maxMoveSpeed);
  this.menuList.scrollLeft -= this.moveSpeed;
  this.checkScrollPosition();
}

/**
 * @method scrollMenuRight
 * @description the speed increases the longer you hold the button
 * @return {undefined}
 */
function scrollMenuRight() {
  this.moveSpeed *= speedMultiplier;
  this.moveSpeed > maxMoveSpeed && (this.moveSpeed = maxMoveSpeed);
  this.menuList.scrollLeft += this.moveSpeed;
  this.checkScrollPosition();
}

/**
 * @method cancelClickTimeout
 * @description TODO
 * @return {undefined}
 */
function cancelClickTimeout() {
  clearTimeout(this.clickTimeout);
}

/**
 * @method cancelInterval
 * @description cancels interval for scrolling
 * @return {undefined}
 */
function cancelInterval() {
  this.moveSpeed = moveStartSpeed;
  clearInterval(this.scrollInterval);
}

/**
 * @method needsArrows
 * @description checks the width of the scrollable content against the viewable navigation width and determines if scrolling is necessary
 *  toggles a hide arrows CSS class
 * @return {undefined}
 */
function needsArrows() {
  const scrollWidth = this.menuList.scrollWidth;
  const navWidth = this.navContainer.getBoundingClientRect().width + 50;
  const requiresArrows = navWidth < scrollWidth;

  if (requiresArrows) {
    this.rootElement.classList.remove(scrollMenuDisabledClass);
  } else {
    this.rootElement.classList.add(scrollMenuDisabledClass);
  }
}

/**
 * @method onContentTouchStart
 * @description emits event when the scroll menu is touch moved by a touch enabled device
 * @return {undefined}
 */
function onContentTouchStart() {
  ee.emit(EVENT_NAMES.scrollMenu.touchMoveStart);
}

/**
 * @method onContentTouchEnd
 * @description emits event when the scroll menu is touch moved by a touch enabled device
 * @return {undefined}
 */
function onContentTouchEnd() {
  ee.emit(EVENT_NAMES.scrollMenu.touchMoveEnd);
}

/**
 * @method checkScrollPosition
 * @description checks the scroll position and toggles disabled class on the arrows
 * @return {undefined}
 */
function checkScrollPosition() {
  const scrollPos = this.menuList.scrollLeft;
  const scrollBoundWidth = this.menuList.getBoundingClientRect().width;
  const scrollWidth = this.menuList.scrollWidth;

  if (this.arrowLeft) {
    if (scrollPos === 0) {
      this.arrowLeft.classList.add(arrowDisabledClass);
    } else {
      this.arrowLeft.classList.remove(arrowDisabledClass);
    }
  }
  if (this.arrowRight) {
    if (this.arrowRight && (scrollPos + scrollBoundWidth) === scrollWidth) {
      this.arrowRight.classList.add(arrowDisabledClass);
    } else {
      this.arrowRight.classList.remove(arrowDisabledClass);
    }
  }
}

/////////////////////////////////////////
/////////////////////////////////////////

const scrollMenuObj = {};

scrollMenuObj.mouseIsDown = false;
scrollMenuObj.mouseLongPress = false;
scrollMenuObj.scrollInterval = null;
scrollMenuObj.clickTimeout = null;

////////////////////////
// init
////////////////////////

scrollMenuObj.init = init;

////////////////////////
// init
////////////////////////

scrollMenuObj.onContentTouchStart = onContentTouchStart;
scrollMenuObj.onContentTouchEnd = onContentTouchEnd;

////////////////////////
// left arrow
////////////////////////

scrollMenuObj.onLeftArrowTouch = onLeftArrowTouch;

scrollMenuObj.onLeftArrowRelease = onLeftArrowRelease;

////////////////////////
// right arrow
////////////////////////

scrollMenuObj.onRightArrowTouch = onRightArrowTouch;

scrollMenuObj.onRightArrowRelease = onRightArrowRelease;

////////////////////////
// menu scrolling
////////////////////////

scrollMenuObj.moveMenuLeft = moveMenuLeft;

scrollMenuObj.moveMenuRight = moveMenuRight;

scrollMenuObj.scrollMenuLeft = scrollMenuLeft;

scrollMenuObj.scrollMenuRight = scrollMenuRight;

scrollMenuObj.cancelClickTimeout = cancelClickTimeout;

scrollMenuObj.cancelInterval = cancelInterval;

scrollMenuObj.needsArrows = needsArrows;

scrollMenuObj.checkScrollPosition = checkScrollPosition;

export default scrollMenuObj;
