/**
 * Custom smooth scroll with easing. Native `behavior: 'smooth'` is too fast/abrupt
 * on most browsers ; this gives us control over duration and easing curve.
 */

const easeInOutCubic = (t: number): number =>
  t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;

export const prefersReducedMotion = (): boolean =>
  typeof window !== 'undefined' &&
  window.matchMedia('(prefers-reduced-motion: reduce)').matches;

type Options = {
  /** Pixels of padding to leave above the element (e.g. fixed navbar). */
  offset?: number;
  /** Animation duration in ms. */
  duration?: number;
};

export const smoothScrollIntoView = (
  el: HTMLElement | null,
  { offset = 112, duration = 800 }: Options = {},
): void => {
  if (!el || typeof window === 'undefined') return;
  if (prefersReducedMotion()) {
    window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - offset, behavior: 'auto' });
    return;
  }

  // The CSS rule `html { scroll-behavior: smooth }` would fight our per-frame
  // scrollTo calls, causing the end of the animation to feel abrupt as the
  // browser tries to "smooth" each tiny step. Temporarily disable it.
  const html = document.documentElement;
  const previousScrollBehavior = html.style.scrollBehavior;
  html.style.scrollBehavior = 'auto';

  const startY = window.scrollY;
  const targetY = el.getBoundingClientRect().top + startY - offset;
  const distance = targetY - startY;

  if (Math.abs(distance) < 2) {
    html.style.scrollBehavior = previousScrollBehavior;
    return;
  }

  const startTime = performance.now();

  const step = (now: number) => {
    const elapsed = now - startTime;
    const t = Math.min(elapsed / duration, 1);
    const eased = easeInOutCubic(t);
    // Use instant behavior to bypass any inherited CSS smooth scroll.
    window.scrollTo({ top: startY + distance * eased, behavior: 'auto' });
    if (t < 1) {
      requestAnimationFrame(step);
    } else {
      // Restore the original CSS rule once the animation is done.
      html.style.scrollBehavior = previousScrollBehavior;
    }
  };

  requestAnimationFrame(step);
};
