import { Box, Flex, ScrollArea } from "@mantine/core";
import { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react";
import useAppShellHeight from "@/hooks/use-appshell-height";
import { SpinnerIcon } from "@phosphor-icons/react";
import { useQueryClient } from "@tanstack/react-query";
import useRouterConfig from "../hooks/use-router-config";
import { useLocation } from "@tanstack/react-router";

const THRESHOLD = 80;

const Pullable: React.FC<PropsWithChildren> = ({ children }) => {
  const height = useAppShellHeight();
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [scrolling, setScrolling] = useState(false);
  const { refresh } = useRouterConfig();
  const queryClient = useQueryClient();
  const location = useLocation();
  const scrollAreaRef = useRef<HTMLDivElement>(null);

  const [pullDistance, setPullDistance] = useState(0);

  const handleScrollPositionChange = useCallback((position: { x: number, y: number }) => {
    const next = position.y < 0 ? Math.abs(position.y) : 0;
    setPullDistance((prev) => (prev === next ? prev : next));
  }, []);

  const scrollY = scrolling ? pullDistance : 0;

  const onTrigger = useCallback(async () => {
    setIsRefreshing(true);
    const minSpinnerDisplay = new Promise((resolve) => setTimeout(resolve, 400));
    if (refresh.length > 0) {
      await Promise.all(
        refresh.map((queryKey) => {
          const keyArray = Array.isArray(queryKey) ? queryKey : [queryKey];
          return queryClient.refetchQueries({ queryKey: keyArray, exact: true });
        })
      );
    }
    await minSpinnerDisplay;
    setIsRefreshing(false);
  }, [refresh, queryClient]);

  useEffect(() => {
    if (!isRefreshing && scrollY > THRESHOLD) {
      onTrigger();
    }
  }, [scrollY, isRefreshing, onTrigger]);

  const iconOpacity = useMemo(() => {
    if (isRefreshing) return 1;
    if (refresh.length === 0) return 0;
    const clampedValue = Math.max(5, Math.min(THRESHOLD, scrollY));

    const min = 5;
    const max = THRESHOLD;
    const range = max - min;

    return (clampedValue - min) / range;
  }, [scrollY, isRefreshing])

  useEffect(() => {
    const scrollWrapper = document.getElementById('scroll-wrapper');
    if (scrollWrapper) {
      scrollWrapper.addEventListener('touchstart', () => {
        setScrolling(true);
      });
      scrollWrapper.addEventListener('touchend', () => {
        setScrolling(false);
      });
    }
  }, []);

  useEffect(() => {
		if (typeof window === 'undefined') return;

		const ac = new AbortController();
		const options = {
			passive: true,
			signal: ac.signal
		};

		window.addEventListener('touchstart', () => setScrolling(true), options);
		window.addEventListener('touchend', () => setScrolling(false), options);

		return () => void ac.abort();
	}, []);

  useEffect(() => {
    const scrollWrapper = document.getElementById('scroll-wrapper');
    if (!scrollWrapper) return;

    const viewport = scrollWrapper.querySelector('.mantine-ScrollArea-viewport') as HTMLElement;
    if (!viewport) return;

    const handleWheel = (e: WheelEvent) => {
      const target = e.target as HTMLElement;

      let element = target;
      while (element && element !== viewport) {
        const overflow = window.getComputedStyle(element).overflow;
        const overflowY = window.getComputedStyle(element).overflowY;
        const overflowX = window.getComputedStyle(element).overflowX;

        if (
          (overflow === 'auto' || overflow === 'scroll' ||
           overflowY === 'auto' || overflowY === 'scroll' ||
           overflowX === 'auto' || overflowX === 'scroll') &&
          element !== viewport
        ) {
          const canScrollY = element.scrollHeight > element.clientHeight;
          const canScrollX = element.scrollWidth > element.clientWidth;

          if ((e.deltaY !== 0 && canScrollY) || (e.deltaX !== 0 && canScrollX)) {
            return;
          }
        }

        element = element.parentElement as HTMLElement;
      }

      viewport.scrollTop += e.deltaY;
      viewport.scrollLeft += e.deltaX;
    };

    scrollWrapper.addEventListener('wheel', handleWheel, { passive: true });

    return () => {
      scrollWrapper.removeEventListener('wheel', handleWheel);
    };
  }, []);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      if (scrollAreaRef.current) {
        const viewport = scrollAreaRef.current.querySelector('.mantine-ScrollArea-viewport') as HTMLElement;
        if (viewport) {
          viewport.scrollTop = 0;
          viewport.scrollLeft = 0;
        }
      }
      setPullDistance(0);
    }, 10);

    return () => clearTimeout(timeoutId);
  }, [location.pathname]);


  return (
    <>
      <Flex
        pos='absolute'
        justify='center'
        align='center'
        w='100%'
        display={scrollY > 20 || isRefreshing ? 'flex' : 'none'}
        opacity={iconOpacity}
        style={{ zIndex: 10 }}
      >
        <SpinnerIcon
          weight="bold"
          size={iconOpacity * 28}
          color='var(--mantine-color-dimmed)'
          style={{
            marginTop: 8,
            transform: iconOpacity === 1 ? undefined : `rotate(${iconOpacity * 360}deg)`,
            animation: iconOpacity === 1 ? 'spin 1s linear infinite' : undefined,
          }}
        />
      </Flex>
      <ScrollArea
        ref={scrollAreaRef}
        id='scroll-wrapper'
        onScrollPositionChange={handleScrollPositionChange}
        type='never' mah='100%' h='100%'
        pt={(scrolling || scrollY > 40) || !isRefreshing ? 0 : 40 - scrollY}
        styles={{
          viewport: {
            '& > *': {
              pointerEvents: 'auto'
            }
          }
        }}
      >
        <Box pt='1rem'pb='0.285rem' mih={height} style={{ boxSizing: 'content-box', pointerEvents: 'auto' }}>
          {children}
        </Box>
      </ScrollArea>
    </>
  )
}

export default Pullable;
