import getRelativePosition from '../utils/get-relative-position';
import { AXIS } from './axis';

export const CURSOR_ITEM_OFFSET = {
  x: 5,
  y: 5
};

export const ATTRIBUTE_SKIP_SORTING = 'sortable-skip';

/**
 * Logic for cloned original item list to perform sorting
 */
export default class MockedSortableItemList {
  /**
   * @param {HTMLElement} element
   */
  setContainer (element) {
    this.elContainer = element;
    /**
     * @type {number}
     */
    this.fromPosition = null;
    /**
     * @type {number}
     */
    this.toPosition = null;
    /**
     * @type {string}
     */
    this.axis = null;
    /**
     * @type {HTMLElement}
     */
    this.sourceItem = null;
    /**
     * @type {HTMLElement}
     */
    this.cursorItem = null;
    /**
     * @type {object}
     */
    this.cursorOffset = null;
  }

  /**
   * @param {string} axis
   */
  setAxis (axis) {
    this.axis = axis;
  }

  hide () {
    this.elContainer.classList.add('hidden');
  }

  reset () {
    this.elContainer.classList.remove('hidden');
    while (this.elContainer.hasChildNodes()) {
      this.elContainer.removeChild(this.elContainer.lastChild);
    }
    this.toPosition = null;
    this.fromPosition = null;
  }

  /**
   * @returns {boolean}
   */
  isSorted () {
    return (
      this.toPosition !== null &&
      this.fromPosition !== null &&
      this.toPosition !== this.fromPosition
    );
  }

  /**
   * @returns {number}
   */
  getFromPosition () {
    return this.fromPosition;
  }

  /**
   * @returns {number}
   */
  getToPosition () {
    return this.toPosition;
  }

  /**
   * @return {HTMLElement}
   */
  getContainer () {
    return this.elContainer;
  }

  /**
   * @param position
   * @returns {Element}
   */
  getItem (position) {
    return this.elContainer.children[position + 1];
  }

  getItemCount () {
    return this.elContainer.children.length - 1;
  }

  /**
   * @param {number} x
   * @param {number} from
   * @returns {number}
   */
  getItemIndexByX (x, from = 0) {
    let i = from;
    let skip = 0;
    while (i + skip < this.getItemCount() - 1) {
      const currentItem = this.getItem(i + skip);
      const nextItem = this.getItem(i + skip + 1);
      if (this.isToSkip(currentItem) || this.isToSkip(nextItem)) {
        skip += 1;
        // eslint-disable-next-line no-continue
        continue;
      } else {
        i += skip;
        skip = 0;
      }
      if (
        currentItem.offsetWidth > nextItem.offsetWidth &&
        currentItem !== this.sourceItem
      ) {
        const keepItemLeft = currentItem.classList.contains('keep-item-left');
        const width = keepItemLeft ? 0 : nextItem.offsetWidth;
        if (currentItem.offsetLeft + width > x) {
          break;
        }
      } else if (nextItem.offsetLeft > x) {
        break;
      }
      if (
        currentItem.offsetLeft + currentItem.offsetWidth / 2 >=
        nextItem.offsetLeft
      ) {
        break;
      }
      i += 1;
    }
    return i;
  }

  /**
   * @param {number} y
   * @returns {number}
   */
  getItemIndexByY (y) {
    let i = 0;
    let result = 0;
    let skip = 0;
    while (i + skip < this.getItemCount() - 1) {
      const currentItem = this.getItem(i + skip);
      const nextItem = this.getItem(i + skip + 1);
      if (this.isToSkip(currentItem) || this.isToSkip(nextItem)) {
        skip += 1;
        // eslint-disable-next-line no-continue
        continue;
      } else {
        i += skip;
        skip = 0;
      }
      if (
        currentItem.offsetHeight > nextItem.offsetHeight &&
        currentItem !== this.sourceItem
      ) {
        if (currentItem.offsetTop + nextItem.offsetHeight > y) {
          break;
        }
      } else if (nextItem.offsetTop > y) {
        break;
      }
      i += 1;
      // catch next row border
      result =
        currentItem.offsetTop + currentItem.offsetHeight / 2 <
        nextItem.offsetTop
          ? i
          : result;
    }
    while (
      result < this.getItemCount() - 1 &&
      this.isToSkip(this.getItem(result))
    ) {
      result += 1;
    }
    return result;
  }

  /**
   * @param {number} x
   * @param {number} y
   * @returns {number}
   */
  getItemIndexByXY (x, y) {
    const from = this.getItemIndexByY(y);
    return this.getItemIndexByX(x, from);
  }

  /**
   * @param {number} fromPosition
   * @param {Event} event
   * @param {string} cursorClass
   * @param {string} cursorSelector
   */
  createCursorItem (fromPosition, event, cursorClass, cursorSelector) {
    this.fromPosition = fromPosition;
    this.toPosition = fromPosition;
    this.sourceItem = this.elContainer.children[fromPosition];
    this.cursorOffset = getRelativePosition(event, this.sourceItem);
    this.cursorItem = this.sourceItem.cloneNode(true);
    const style = getComputedStyle(this.sourceItem);
    if (style.display === 'block') {
      this.cursorItem.style.setProperty(
        'width',
        `${this.sourceItem.clientWidth}px`
      );
      this.cursorItem.style.setProperty('top', '0');
      this.cursorItem.style.setProperty('margin-top', '0');
    }
    this.sourceItem.classList.add('up-source-item');
    this.cursorItem.classList.add('up-cursor-item');
    const cursorItemPresentation = cursorSelector
      ? this.cursorItem.querySelector(cursorSelector)
      : this.cursorItem;
    cursorItemPresentation.classList.add('up-cursor-item-presentation');
    if (cursorClass) {
      cursorItemPresentation.classList.add(cursorClass);
    }
    this.sourceItem.setAttribute('aria-hidden', 'true');
    this.cursorItem.setAttribute('aria-role', 'button');
    this.cursorItem.setAttribute('aria-pressed', 'true');
    this.cursorItem.setAttribute('tabindex', '0');
    this.elContainer.prepend(this.cursorItem);
    if (event.type.indexOf('key') === 0) {
      this.applyMovement();
      this.cursorItem.focus();
      this.stickCursorItemToSourceItem();
    } else {
      this.move(event);
    }
  }

  /**
   * @param {Event} event
   */
  move (event) {
    if (this.getItemCount() > 0) {
      const containerPosition = getRelativePosition(event, this.elContainer);
      let offsetX =
        containerPosition.x - this.cursorOffset.x - containerPosition.pl;
      let offsetY =
        containerPosition.y - this.cursorOffset.y - containerPosition.pt;
      switch (this.axis) {
      case AXIS.x:
        offsetY = 0;
        this.toPosition = this.getItemIndexByX(containerPosition.x);
        break;
      case AXIS.y:
        offsetX = 0;
        this.toPosition = this.getItemIndexByY(containerPosition.y);
        break;
      case AXIS.xy:
        this.toPosition = this.getItemIndexByXY(
          containerPosition.x,
          containerPosition.y
        );
        break;
      default:
        // eslint-disable-next-line no-throw-literal
        throw `Unknown axis was passed '${this.axis}'`;
      }
      this.cursorItem.style.setProperty(
        'transform',
        `translate(${offsetX + 5}px, ${offsetY + 5}px)`
      );
      this.applyMovement();
    }
  }

  moveUp () {
    const prevPosition = this.searchPosition(this.toPosition, -1);
    if (prevPosition !== false) {
      this.toPosition = prevPosition;
      this.applyMovement();
      this.stickCursorItemToSourceItem();
    }
  }

  moveDown () {
    const nextPosition = this.searchPosition(this.toPosition, 1);
    if (nextPosition !== false) {
      this.toPosition = nextPosition;
      this.applyMovement();
      this.stickCursorItemToSourceItem();
      return true;
    }
    return false;
  }

  searchPosition (position, step) {
    let newPosition = position;
    while (
      newPosition + step >= 0 &&
      newPosition + step < this.getItemCount()
    ) {
      newPosition += step;
      if (!this.isToSkip(this.getItem(newPosition))) {
        return newPosition;
      }
    }
    return false;
  }

  /**
   * @private
   */
  stickCursorItemToSourceItem () {
    const style = window.getComputedStyle(this.elContainer);
    const x =
      this.axis !== AXIS.y
        ? this.sourceItem.offsetLeft - parseFloat(style.paddingLeft)
        : CURSOR_ITEM_OFFSET.x;
    const y =
      this.axis !== AXIS.x
        ? this.sourceItem.offsetTop - parseFloat(style.paddingTop)
        : CURSOR_ITEM_OFFSET.y;
    this.cursorItem.style.setProperty('transform', `translate(${x}px, ${y}px)`);
  }

  /**
   * @private
   */
  applyMovement () {
    let targetItem = this.getItem(this.toPosition);
    if (targetItem !== this.sourceItem) {
      if (targetItem && this.toPosition < this.getItemCount() - 1) {
        if (this.toPosition > 0) {
          const previousItem = this.getItem(this.toPosition - 1);
          if (previousItem === this.sourceItem) {
            targetItem = this.getItem(this.toPosition + 1);
          }
        }
        const targetItemBefore = targetItem.getBoundingClientRect();
        const isMoveBack =
          targetItem.offsetTop < this.sourceItem.offsetTop ||
          (targetItem.offsetTop === this.sourceItem.offsetTop &&
            targetItem.offsetLeft < this.sourceItem.offsetLeft);
        this.elContainer.insertBefore(this.sourceItem, targetItem);
        if (isMoveBack) {
          const targetItemAfter = targetItem.getBoundingClientRect();
          /* istanbul ignore next */
          if (
            targetItemBefore.left === targetItemAfter.left &&
            targetItemBefore.top === targetItemAfter.top
          ) {
            targetItem.classList.add('keep-item-left');
          }
        }
      } else {
        this.elContainer.appendChild(this.sourceItem);
      }
    }
  }

  /**
   * @param {HTMLElement} item
   * @returns {boolean}
   */
  /* eslint class-methods-use-this: "off" */
  isToSkip (item) {
    return (
      item.hasAttribute(ATTRIBUTE_SKIP_SORTING) &&
      [true, 'true', ATTRIBUTE_SKIP_SORTING].includes(item.getAttribute(ATTRIBUTE_SKIP_SORTING))
    );
  }

  getCursorPageX () {
    if (this.cursorItem) {
      const rect = this.cursorItem.getBoundingClientRect();
      /* istanbul ignore next */
      const scrollLeft = document.documentElement.scrollLeft
        ? document.documentElement.scrollLeft
        : document.body.scrollLeft;
      return rect.left + scrollLeft;
    }
    return 0;
  }

  getCursorPageY () {
    if (this.cursorItem) {
      const rect = this.cursorItem.getBoundingClientRect();
      /* istanbul ignore next */
      const scrollTop = document.documentElement.scrollTop
        ? document.documentElement.scrollTop
        : document.body.scrollTop;
      return rect.top + scrollTop;
    }
    return 0;
  }

  finishSorting () {
    this.elContainer.querySelectorAll('.keep-item-left').forEach((item) => {
      item.classList.remove('keep-item-left');
    });
  }
}
