/**
 * Debounce a function to delay invoking until wait (ms) have elapsed since the last invocation.
 * @param {function(...*): *} fn The function to be debounced.
 * @param {number} wait Milliseconds to wait before invoking again.
 * @return {function(...*): void} The debounced function.
 */
function debounce(fn, wait) {
  /**
   * A cached setTimeout handler.
   * @type {number | undefined}
   */
  let timer;

  /**
   * @returns {void}
   */
  function debounced() {
    const context = this;
    const args = arguments;

    clearTimeout(timer);
    timer = setTimeout(function () {
      return fn.apply(context, args);
    }, wait);
  }

  return debounced;
}

/**
 * Prettify a filename from error stacks into the desired format.
 * @param {string} filename The filename to be formatted.
 * @returns {string} The formatted filename.
 */
function formatFilename(filename) {
  // Strip away protocol and domain for compiled files
  const htmlMatch = /^https?:\/\/(.*)\/(.*)/.exec(filename);
  if (htmlMatch && htmlMatch[1] && htmlMatch[2]) {
    return htmlMatch[2];
  }

  // Strip everything before the first directory for source files
  const sourceMatch = /\/.*?([^./]+[/|\\].*)$/.exec(filename);
  if (sourceMatch && sourceMatch[1]) {
    return sourceMatch[1].replace(/\?$/, '');
  }

  // Unknown filename type, use it as is
  return filename;
}

/**
 * Remove all children of an element.
 * @param {HTMLElement} element A valid HTML element.
 * @param {number} [skip] Number of elements to skip removing.
 * @returns {void}
 */
function removeAllChildren(element, skip) {
  /** @type {Node[]} */
  const childList = Array.prototype.slice.call(
    element.childNodes,
    typeof skip !== 'undefined' ? skip : 0,
  );

  for (let i = 0; i < childList.length; i += 1) {
    element.removeChild(childList[i]);
  }
}

module.exports = {
  debounce: debounce,
  formatFilename: formatFilename,
  removeAllChildren: removeAllChildren,
};
