/**
 * 返回一个自带消抖效果的函数，下文用fnDebounced表示。
 *
 * fn: 需要被消抖的函数
 * delay: 消抖时长
 * isImmediateCall: 是否在一组操作中的第一次调用时立即执行fn
 * isRememberLastCall：是否在一组中最后一次调用后等delay时长再执行fn
 * 
 * 如果isRememberLastCall为false，意味着fn不会被延迟执行，所以fnDebounced执行时，要么在内部调用fn，同步返回fn返回值；要么内部决定本次不调用fn，同步返回null。
 * 如果isRememberLastCall为true，意味着fn可能被延迟执行，所以fnDebounced会返回一个Promise,在fn被调用时用其返回值resolve该Promise，或者在fn的延时调用计划被取消时用'canceled'resolve该Promise。(不宜reject，否则又没有人去catch，会导致浏览器报错。)
 */
export function debounce(fn, delay = 250, isImmediateCall = false, isRememberLastCall = true) {
  console.assert(isImmediateCall || isRememberLastCall, 'isImmediateCall 和 isRememberLastCall 至少应有一个是true，否则没有意义！')
  let timer = null
  let retPromiseLastTimeResolver = null
  // 上次调用的时刻
  let lastCallTime = 0

  if (isImmediateCall && !isRememberLastCall) {
    return function (...args) {
      let ret = null
      const currentTime = Date.now()
      if (currentTime - lastCallTime >= delay) {
        ret = fn.apply(this, args)
      }
      lastCallTime = currentTime
      return ret
    }
  } else if (!isImmediateCall && isRememberLastCall) {
    return function (...args) {
      if (timer) {
        clearTimeout(timer)
        timer = null
      }
      if (retPromiseLastTimeResolver) {
        retPromiseLastTimeResolver('canceled')
        retPromiseLastTimeResolver = null
      }
      const ret = new Promise((resolve, reject) => {
        retPromiseLastTimeResolver = resolve
        timer = setTimeout(() => {
          timer = null
          retPromiseLastTimeResolver = null
          resolve(fn.apply(this, args))
        }, delay)
      })
      return ret
    }
  } else if (isImmediateCall && isRememberLastCall) {
    return function (...args) {
      const currentTime = Date.now()
      if (currentTime - lastCallTime >= delay) { // 一组操作中的第一次
        const res = fn.apply(this, args) 
        lastCallTime = currentTime
        return Promise.resolve(res)
      } else { // 一组中的后续调用
        if (timer) { // 在此之前存在中间调用
          lastCallTime = currentTime
          clearTimeout(timer)
          timer = null
        }
        if (retPromiseLastTimeResolver) {
          retPromiseLastTimeResolver('canceled')
          retPromiseLastTimeResolver = null
        }
        const ret = new Promise((resolve, reject) => {
          retPromiseLastTimeResolver = resolve
          timer = setTimeout(() => {
            lastCallTime = 0
            timer = null
            retPromiseLastTimeResolver = null
            resolve(fn.apply(this, args))
          }, delay)
        })
        return ret
      }
    }
  } else {
    console.error('不应该执行到这里！')
  }
}

export default {
  debounce,
}