/* eslint-disable react-hooks/exhaustive-deps */
import { AnimatePresence, motion, useMotionValue } from 'framer-motion';
import { useCallback, useRef, useState } from 'react';

import { useRouteChangeEffect } from './use-route-change-effect';
import { useScrollEventListeners } from './use-scroll-event-listeners';
import { useScrollTarget } from './use-scroll-target';

const TIME_OUT = 5000;
const THRASH_HOLD = 30;

let isScrollingBackToTop = false;
let lastScrollY = 0;
let isTouching = false;

export const TopButton = () => {
  const scrollTarget = useScrollTarget();
  const scrollY = useMotionValue(scrollTarget.current?.scrollTop ?? 0);

  const scrollTimeOutRef = useRef<NodeJS.Timeout | undefined>(undefined);
  const [showButton, setShowButton] = useState<boolean | undefined>();

  const reset = useCallback(() => {
    setShowButton(false);
    isScrollingBackToTop = false;
    isTouching = false;
    if (scrollTarget.current) {
      scrollTarget.current.scrollTop = 0;
    }
    clearTimeout(scrollTimeOutRef.current);
  }, []);

  const initialize = useCallback(() => {
    reset();
    lastScrollY = 0;
  }, []);

  const scrollBackToTop = useCallback(() => {
    reset();
  }, []);

  const onScroll = () => {
    if (isTouching) return;
    const currentScrollY = scrollTarget.current?.scrollTop ?? 0;
    const direction = currentScrollY > lastScrollY ? 'down' : 'up';
    lastScrollY = currentScrollY <= 0 ? 0 : currentScrollY;

    scrollY.set(currentScrollY);
    clearTimeout(scrollTimeOutRef.current);

    setShowButton(direction === 'down' ? false : currentScrollY > THRASH_HOLD);

    if (!isScrollingBackToTop && direction === 'down') {
      scrollTimeOutRef.current = setTimeout(() => {
        setShowButton(true);
      }, TIME_OUT);
    }
    if (currentScrollY === 0) {
      isScrollingBackToTop = false;
    }
  };

  const onTouchStart = useCallback(() => {
    isTouching = true;
  }, []);
  const onTouchMove = useCallback(() => {
    isTouching = true;
  }, []);
  const onTouchEnd = useCallback(() => {
    isTouching = false;
  }, []);

  useScrollEventListeners({
    scrollTarget,
    onScroll,
    onTouchStart,
    onTouchMove,
    onTouchEnd,
  });

  useRouteChangeEffect({
    scrollTarget,
    onScroll,
    onTouchStart,
    onTouchMove,
    onTouchEnd,
    callback: initialize,
  });
  return (
    <AnimatePresence>
      {showButton && (
        <motion.button
          onClick={scrollBackToTop}
          className={'btn goto-top-btn'}
          initial={{ translateY: '110%' }}
          animate={{ translateY: '0%' }}
        >
          위로
        </motion.button>
      )}
    </AnimatePresence>
  );
};
