function debounce(func, wait, immediate) {
  /** @type {any} */
  let timeout;
  /** @type {IArguments|null} */
  let args;
  /** @type {any} */
  let context;
  /** @type {number} */
  let timestamp;
  /** @type {any} */
  let result;

  function later() {
    const last = Date.now() - timestamp;
    if ((last < wait) && (last >= 0)) {
      timeout = setTimeout(later, wait - last);
    } else {
      timeout = null;
      if (!immediate) {
        result = func.apply(context, args);
        if (!timeout) {
          context = null;
          args = null;
        }
      }
    }
  }

  return function after() {
    context = this;
    args = arguments;
    timestamp = Date.now();
    const callNow = immediate && !timeout;
    if (!timeout) {
      timeout = setTimeout(later, wait);
    }
    if (callNow) {
      result = func.apply(context, args);
      context = null;
      args = null;
    }
    return result;
  };
}
@param {Function} func
@param {number} wait
@param {boolean} [immediate]
@returns {Function}