export default class Util {
  static forEach(elements, handler) {
    elements = elements || [];
    const promises = [];
    for (let i = 0; i < elements.length; i++) {
      const result = handler(elements[i], i);
      if (result instanceof Promise) {
        promises.push(result);
      }
    }
    return Promise.all(promises);
  }

  static getScrollTop() {
    return (document.documentElement ?? document.body).scrollTop;
  }

  static isMobile() {
    return window.matchMedia('only screen and (max-width: 680px)').matches;
  }

  static isTocStatic() {
    return document.getElementById('toc-static').dataset.kept === 'true' || window.matchMedia('only screen and (max-width: 960px)').matches;
  }

  /**
   * add animate to element
   * @param {Element} element animate element
   * @param {String|Array<String>} animation animation name
   * @param {Boolean} reserved reserved animation
   * @param {Function} callback remove callback
   */
  static animateCSS(element, animation, reserved, callback) {
    !Array.isArray(animation) && (animation = [animation]);
    element.classList.add('animate__animated', ...animation);
    element.addEventListener('animationend', () => {
      !reserved && element.classList.remove('animate__animated', ...animation);
      typeof callback === 'function' && callback();
    }, { once: true });
  }

  /**
   * date validator
   * @param {*} date may be date or not
   * @returns {Boolean}
   */
  static isValidDate(date) {
    return date instanceof Date && !isNaN(date.getTime());
  }

  /**
   * scroll some element into view
   * @param {String} selector element to scroll
   */
  static scrollIntoView(selector) {
    const element = selector.startsWith('#')
      ? document.getElementById(selector.slice(1))
      : document.querySelector(selector);
    element?.scrollIntoView({
      behavior: 'smooth'
    });
  }

  /**
   * get a hidden element for temporary use
   * @returns {Object} { $el: Element, destroy: Function }
   */
  static getStagingDOM() {
    const stagingElement = document.createElement('div')
    stagingElement.style.display = 'none';
    stagingElement.dataset.stagingId = Math.random().toString(36).slice(2);
    document.body.appendChild(stagingElement);

    return {
      $el: stagingElement,
      stage(dom) {
        stagingElement.innerHTML = '';
        stagingElement.appendChild(dom);
      },
      contentAsHtml() {
        return stagingElement.innerHTML;
      },
      contentAsText() {
        return stagingElement.innerText;
      },
      contentAsJson() {
        return JSON.parse(stagingElement.innerHTML);
      },
      destroy() {
        document.body.removeChild(stagingElement);
      }
    }
  }

  /**
   * copy text to clipboard
   * @param {String} text text to copy
   * @returns {Promise} promise
   */
  static copyText(text) {
    if (navigator.clipboard) {
      this.copyText = (text) => navigator.clipboard.writeText(text);
      return this.copyText(text);
    }
    this.copyText = (text) => new Promise((resolve, reject) => {
      const input = document.createElement('input');
      input.value = text;
      document.body.appendChild(input);
      input.select();
      if (document.execCommand('copy')) {
        document.body.removeChild(input);
        resolve();
      } else {
        reject();
      }
    });
    return this.copyText(text);
  }

  /**
   * check if a string is a JS object string
   * @example isObjectLiteral("{a:1,b:2}") // true
   * @param {String} str string to check
   * @returns {Boolean} whether the string is a JS object string
   */
  static isObjectLiteral(str) {
    if (typeof str !== 'string') {
      return false;
    }
    str = str.replace(/\s+/g, ' ').trim().replace(/;$/, '')
    if (str.startsWith('{') && str.endsWith('}')) {
      return true;
    }
    return false;
  }

  static HTMLEscape(str) {
    return str.replace(/[&<>"']/g, char => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    })[char]);
  }
}
