/* == class 相关 == */
const any = (obj: any) => obj;

// 添加 class
export const addClass = (element: Element, ...className: string[]): void => {
  element.classList.add(...className);
};

// 移除 class
export const removeClass = (element: Element, ...className: string[]): void => {
  element.classList.remove(...className);
};

// 是否包含指定 class
export const hasClass = (element: Element, className: string): boolean => {
  return element.classList.contains(className);
};

// 切换指定 class
// 若为移除，则返回 false；若为添加，则返回 true
export const toggleClass = (element: Element, className: string): boolean => {
  return element.classList.toggle(className);
};

/* == DOM 相关 == */

// 查找第一个指定的css选择器
export const query = (startNode: Element, cssSelector: string): Element | null => {
  return startNode.querySelector(cssSelector);
};

// 查找所有指定的css选择器
export const queryAll = (startNode: Element, cssSelector: string): NodeListOf<Element> => {
  return (startNode || document).querySelectorAll(cssSelector);
};

// 返回或者设置 innerHTML
// eslint-disable-next-line consistent-return
export const html = (element: Element, innerHTML?: string): string | void => {
  if (typeof innerHTML === 'undefined') {
    return element.innerHTML;
  }
  element.innerHTML = innerHTML;
};

// 返回或者设置 innerText
// eslint-disable-next-line consistent-return
export const text = (element: HTMLElement, innerText?: string): string | void => {
  if (typeof innerText === 'undefined') {
    return element.innerText;
  }
  element.innerText = innerText;
};

// 创建元素
export const createElement = (tagName: string): HTMLElement => {
  return document.createElement(tagName);
};

// 在container内部的后面挂载元素
export const append = (container: Element, ...element: Element[]): void => {
  container.append(...element);
};

// 在container内部的前面挂载元素
export const prepend = (container: Element, ...element: Element[]): void => {
  container.prepend(...element);
};

// 移除元素
export const remove = (element: Element | null): void => {
  element && element.remove();
};

// 绑定事件
export const on = (element: Element, eventName: string, callback: (event: Event) => void, useCapture = false): void => {
  element.addEventListener(eventName, callback, useCapture);
};

// 解绑事件
export const off = (element: Element, eventName: string, callback: (event: Event) => void): void => {
  element.removeEventListener(eventName, callback);
};

// hover
export const hover = (element: Element, handlerIn: (e: Event) => void, handlerOut: (e: Event) => void): void => {
  on(element, 'mouseenter', handlerIn);
  on(element, 'mouseleave', handlerOut);
};

// wheel滚轮事件
export const wheel = (element: Element, handlerWheel: (e: Event) => void): void => {
  on(element, 'mousewheel', handlerWheel);
};

// 解析字符串生成DOM元素
export const parse = (innerHTML: string): Element => {
  const container = createElement('div');
  html(container, innerHTML.trim());
  return container.firstChild as Element;
};

// 设置元素样式
export const style = (element: HTMLElement, styleObj: { [key in keyof CSSStyleDeclaration]?: string }): void => {
  const keys = Object.keys(styleObj);
  keys.forEach(key => {
    any(element.style)[key] = any(styleObj)[key];
  });
};

// 清空dom元素
export const empty = (element: Element): void => {
  let child;
  // eslint-disable-next-line no-cond-assign
  while ((child = element.lastChild)) {
    element.removeChild(child);
  }
};

// 获取元素的位置
export const getElementOffset = (element: HTMLElement) => {
  const offset = { left: 0, top: 0 };
  let current: HTMLElement = element.offsetParent as any;

  offset.left += element.offsetLeft;
  offset.top += element.offsetTop;

  while (current !== null) {
    offset.left += current.offsetLeft;
    offset.top += current.offsetTop;
    current = current.offsetParent as any;
  }
  return offset;
};

/**
 * Returns caret position in text input
 *
 * @author http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
 * @return {Number}
 */
export const getCaretPosition = (el: HTMLInputElement) => {
  if (el.selectionStart) {
    return el.selectionStart;
  }
  if ((document as any).selection) {
    // IE8
    el.focus();

    const r = (document as any).selection.createRange();

    if (r == null) {
      return 0;
    }
    const re = (el as any).createTextRange();
    const rc = re.duplicate();

    re.moveToBookmark(r.getBookmark());
    rc.setEndPoint('EndToStart', re);

    return rc.text.length;
  }

  return 0;
};

/**
 * Sets caret position in text input.
 *
 * @author http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/
 * @param {Element} element
 * @param {Number} pos
 * @param {Number} endPos
 */
export const setCaretPosition = (element: HTMLInputElement, pos: number, endPos: number) => {
  if (endPos === 0) {
    endPos = pos;
  }
  if (element.setSelectionRange) {
    element.focus();

    try {
      element.setSelectionRange(pos, endPos);
    } catch (err) {
      const elementParent = element.parentNode as HTMLElement;
      const parentDisplayValue = elementParent.style.display;
      elementParent.style.display = 'block';
      element.setSelectionRange(pos, endPos);
      elementParent.style.display = parentDisplayValue;
    }
  } else if ((element as any).createTextRange) {
    // IE8
    const range = (element as any).createTextRange();
    range.collapse(true);
    range.moveEnd('character', endPos);
    range.moveStart('character', pos);
    range.select();
  }
};
