import { isNumber, isUndefined } from 'lodash';

const MAX_DURING_TIME = 468;
const MIN_DURING_TIME = 166;

function ease(k: number) {
  return 0.5 * (1 - Math.cos(Math.PI * k));
}

export function scrollTo(
  el: HTMLElement,
  options: {
    top?: number;
    left?: number;
    behavior?: 'instant' | 'smooth';
    during?: number;
  },
  onEnd?: () => void
) {
  let tid: number | null = null;

  if (options.behavior === 'instant') {
    if (!isUndefined(options.top)) {
      el.scrollTop = options.top;
    }
    if (!isUndefined(options.left)) {
      el.scrollLeft = options.left;
    }
    onEnd?.();
  } else {
    const startTime = window.performance.now();
    const startTop = el.scrollTop;
    const startLeft = el.scrollLeft;
    const top = options.top;
    const left = options.left;
    const distance = Math.max(isNumber(top) ? Math.abs(top - startTop) : 0, isNumber(left) ? Math.abs(left - startLeft) : 0);
    const during = Math.max(
      Math.sin((Math.PI / 2) * Math.min(distance / (isNumber(top) ? el.clientHeight : el.clientWidth), 1)) *
        (options.during ?? MAX_DURING_TIME),
      MIN_DURING_TIME
    );

    const step = () => {
      const time = window.performance.now();
      const elapsed = Math.min((time - startTime) / during, 1);
      const speed = ease(elapsed);

      let isFinish = true;
      if (!isUndefined(top)) {
        const currentTop = startTop + (top - startTop) * speed;
        el.scrollTop = currentTop;
        if (currentTop !== top) {
          isFinish = false;
        }
      }
      if (!isUndefined(left)) {
        const currentLeft = startLeft + (left - startLeft) * speed;
        el.scrollLeft = currentLeft;
        if (currentLeft !== left) {
          isFinish = false;
        }
      }

      if (!isFinish) {
        tid = window.requestAnimationFrame(() => {
          tid = null;
          step();
        });
      } else {
        onEnd?.();
      }
    };
    step();
  }

  return () => {
    tid && clearTimeout(tid);
  };
}