import type { FunctionArgs } from '@vueuse/core';

import { upperFirst } from 'lodash-es';



export interface ViewportOffsetResult {

  left: number;

  top: number;

  right: number;

  bottom: number;

  rightIncludeBody: number;

  bottomIncludeBody: number;

}



export function getBoundingClientRect(element: Element): DOMRect | number {

  if (!element || !element.getBoundingClientRect) {

    return 0;

  }

  return element.getBoundingClientRect();

}



function trim(string: string) {

  return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');

}



/* istanbul ignore next */

export function hasClass(el: Element, cls: string) {

  if (!el || !cls) return false;

  if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');

  if (el.classList) {

    return el.classList.contains(cls);

  } else {

    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;

  }

}



/* istanbul ignore next */

export function addClass(el: Element, cls: string) {

  if (!el) return;

  let curClass = el.className;

  const classes = (cls || '').split(' ');



  for (let i = 0, j = classes.length; i < j; i++) {

    const clsName = classes[i];

    if (!clsName) continue;



    if (el.classList) {

      el.classList.add(clsName);

    } else if (!hasClass(el, clsName)) {

      curClass += ' ' + clsName;

    }

  }

  if (!el.classList) {

    el.className = curClass;

  }

}



/* istanbul ignore next */

export function removeClass(el: Element, cls: string) {

  if (!el || !cls) return;

  const classes = cls.split(' ');

  let curClass = ' ' + el.className + ' ';



  for (let i = 0, j = classes.length; i < j; i++) {

    const clsName = classes[i];

    if (!clsName) continue;



    if (el.classList) {

      el.classList.remove(clsName);

    } else if (hasClass(el, clsName)) {

      curClass = curClass.replace(' ' + clsName + ' ', ' ');

    }

  }

  if (!el.classList) {

    el.className = trim(curClass);

  }

}

/**

 * Get the left and top offset of the current element

 * left: the distance between the leftmost element and the left side of the document

 * top: the distance from the top of the element to the top of the document

 * right: the distance from the far right of the element to the right of the document

 * bottom: the distance from the bottom of the element to the bottom of the document

 * rightIncludeBody: the distance between the leftmost element and the right side of the document

 * bottomIncludeBody: the distance from the bottom of the element to the bottom of the document

 *

 * @description:

 */

export function getViewportOffset(element: Element): ViewportOffsetResult {

  const doc = document.documentElement;



  const docScrollLeft = doc.scrollLeft;

  const docScrollTop = doc.scrollTop;

  const docClientLeft = doc.clientLeft;

  const docClientTop = doc.clientTop;



  const pageXOffset = window.pageXOffset;

  const pageYOffset = window.pageYOffset;



  const box = getBoundingClientRect(element);



  const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;



  const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);

  const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);

  const offsetLeft = retLeft + pageXOffset;

  const offsetTop = rectTop + pageYOffset;



  const left = offsetLeft - scrollLeft;

  const top = offsetTop - scrollTop;



  const clientWidth = window.document.documentElement.clientWidth;

  const clientHeight = window.document.documentElement.clientHeight;

  return {

    left: left,

    top: top,

    right: clientWidth - rectWidth - left,

    bottom: clientHeight - rectHeight - top,

    rightIncludeBody: clientWidth - left,

    bottomIncludeBody: clientHeight - top,

  };

}



export function hackCss(attr: string, value: string) {

  const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];



  const styleObj: any = {};

  prefix.forEach((item) => {

    styleObj[`${item}${upperFirst(attr)}`] = value;

  });

  return {

    ...styleObj,

    [attr]: value,

  };

}



/* istanbul ignore next */

export function on(element: Element | HTMLElement | Document | Window, event: string, handler: EventListenerOrEventListenerObject): void {

  if (element && event && handler) {

    element.addEventListener(event, handler, false);

  }

}



/* istanbul ignore next */

export function off(element: Element | HTMLElement | Document | Window, event: string, handler: Fn): void {

  if (element && event && handler) {

    element.removeEventListener(event, handler, false);

  }

}



/* istanbul ignore next */

export function once(el: HTMLElement, event: string, fn: EventListener): void {

  const listener = function (this: any, ...args: unknown[]) {

    if (fn) {

      fn.apply(this, args);

    }

    off(el, event, listener);

  };

  on(el, event, listener);

}



export function useRafThrottle<T extends FunctionArgs>(fn: T): T {

  let locked = false;

  // @ts-ignore

  return function (...args: any[]) {

    if (locked) return;

    locked = true;

    window.requestAnimationFrame(() => {

      // @ts-ignore

      fn.apply(this, args);

      locked = false;

    });

  };

}



/**

 * 查找父级元素,直到找到符合条件的元素

 * @param element 当前元素

 * @param checkFn 判断条件

 */

export function queryParentElement(element: HTMLElement, checkFn: (node: HTMLElement) => boolean): HTMLElement | null {

  let ele: HTMLElement | null = element;

  while (ele) {

    try {

      if (checkFn(ele)) {

        return ele;

      }

      ele = ele.parentElement;

    } catch (e) {

      return null;

    }

  }

  return null;

}