// 在下一个事件循环里执行任务
export const nextTick = function (fn: Function, ctx?: any): () => void {
  let pending = false;
  let timerFunc: Function | null = null;
  let handle = (): void => {
    pending = false;
    ctx ? fn.call(ctx) : fn();
  };

  // 支持MutationObserver接口的话使用MutationObserver 
  if (typeof MutationObserver !== 'undefined') {
    let counter = 1;
    let observer = new MutationObserver(handle);
    let textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true // 设为 true 表示监视指定目标节点或子节点树中节点所包含的字符数据的变化
    });
    timerFunc = function (): void {
      counter = (counter + 1) % 2; // counter会在0和1两者循环变化
      textNode.data = String(counter); // 节点变化会触发回调handle
    };
  } else {
    // 否则使用定时器
    timerFunc = setTimeout;
  }

  return function (): void {
    if (pending) return;
    pending = true;
    timerFunc(handle, 0);
  };
};

// 节流函数
export const throttle = <T extends (...args: any[]) => any>(
  fn: T,
  time: number = 300,
  ctx?: any
): ((...args: Parameters<T>) => void) => {
  let timer: ReturnType<typeof setTimeout> | null = null;
  return (...args: Parameters<T>): void => {
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.call(ctx, ...args);
      timer = null;
    }, time);
  };
};

// 防抖函数
export const debounce = <T extends (...args: any[]) => any>(
  fn: T,
  wait: number = 300,
  ctx?: any
): ((...args: Parameters<T>) => void) => {
  let timeout: ReturnType<typeof setTimeout> | null = null;

  return (...args: Parameters<T>): void => {
    if (timeout) clearTimeout(timeout);
    const callNow = !timeout;
    timeout = setTimeout(() => {
      timeout = null;
      fn.apply(ctx, args);
    }, wait);
    if (callNow) fn.apply(ctx, args);
  };
};

// 异步执行任务队列
export const asyncRun = (
  taskList: Array<() => void>,
  callback: () => void = () => { }
): void => {
  let index = 0;
  let len = taskList.length;
  if (len <= 0) {
    return callback();
  }
  let loop = (): void => {
    if (index >= len) {
      callback();
      return;
    }
    taskList[index]();
    setTimeout(() => {
      index++;
      loop();
    }, 0);
  };
  loop();
};