/**
 * 滚动条逸出到指定位置动画
 */

interface ScrollEaseOutOption {
  top?: number;
  withoutIsAnimating?: boolean;
  callback?: (isEnd: boolean) => void;
}

const defaultOption = {
  top: 0,
  withoutIsAnimating: false,
  callback: () => {},
};

// 用来判断当前dom是否在执行动画
const scrollMap = new WeakMap<HTMLElement, ScrollEaseOutOption>();

// 动画是否正在执行
export const scrollEaseOutAnimation = {
  isAnimating: false,
};

export const scrollEaseOut = (target: HTMLElement, option: ScrollEaseOutOption) => {
  const preOption = scrollMap.get(target);
  // 是否之前的动画执行结束
  const isAnimationFinished = !preOption;
  // 用于判断是否用户有手动操作，用户手动操作优先级高
  let preScrollTop = -1;

  scrollMap.set(target, Object.assign(defaultOption, option));

  const scroll = () => {
    const {
      top = defaultOption.top,
      withoutIsAnimating = defaultOption.withoutIsAnimating,
      callback,
    } = scrollMap.get(target) || defaultOption;

    if (!withoutIsAnimating) {
      scrollEaseOutAnimation.isAnimating = true;
    }

    const { scrollTop, scrollHeight, clientHeight } = target;

    let destination = top;
    if (destination < 0) destination = 0;
    if (destination > scrollHeight - clientHeight) destination = scrollHeight - clientHeight;

    // 用户在操作
    const isUserOperate = preScrollTop >= 0 && Math.abs(preScrollTop - scrollTop) > 1;

    // 动画执行完
    if (Math.abs(destination - scrollTop) <= 1 || isUserOperate) {
      if (!isUserOperate) {
        target.scrollTo({ top: destination });
      }

      // 动画执行结束
      scrollMap.delete(target);
      setTimeout(() => {
        scrollEaseOutAnimation.isAnimating = false;
      }, 200);
      preScrollTop = -1;
      callback?.(true);

      return;
    }

    // 按段设置滚动频率
    const frequency = 14;
    target.scrollTo({ top: Math.ceil(scrollTop + (destination - scrollTop) / frequency) });
    // scrollTop属性 只支持整数和0.5级别的浮点数，极大可能上面计算结果不会改变
    if (target.scrollTop === scrollTop) {
      target.scrollTo({ top: target.scrollTop + (destination > scrollTop ? 1 : -1) });
    }
    preScrollTop = target.scrollTop;
    callback?.(false);

    requestAnimationFrame(scroll);
  };

  if (isAnimationFinished) {
    requestAnimationFrame(scroll);
  }
};
