import { ActionIcon, Box, Button, Flex, ScrollArea } from "@mantine/core";
import { PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from "react";
import useAppShellHeight from "@/hooks/use-appshell-height";
import { ArrowClockwiseIcon, 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;

interface PullableProps extends PropsWithChildren {
  scrollPosition: { x: number, y: number };
  onScrollPositionChange: (position: { x: number, y: number }) => void;
}

/**
 * Pullable is a component that allows the user to pull down to refresh the page
 */
const Pullable: React.FC<PullableProps> = ({ children, scrollPosition, onScrollPositionChange }) => {
  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 scrollY = useMemo(() => scrollPosition.y < 0 && scrolling ? Math.abs(scrollPosition.y) : 0, [scrollPosition.y, scrolling]);

  const onTrigger = useCallback(async () => {
    setIsRefreshing(true);
    if (refresh.length > 0) {
      // TODO: Remove this after testing - or does the delay help ux?
      await new Promise(resolve => setTimeout(resolve, 1000));
      refresh.forEach(async (queryKey) => {
        const keyArray = Array.isArray(queryKey) ? queryKey : [queryKey];
        await queryClient.refetchQueries({ queryKey: keyArray, exact: true });
      });
    }
    setIsRefreshing(false);
  }, [refresh]);

  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();
	}, []);

  // Fix wheel scrolling over child elements
  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;

      // Check if the target is inside a nested scrollable container
      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 we found a scrollable ancestor (not the main viewport), don't interfere
        if (
          (overflow === 'auto' || overflow === 'scroll' ||
           overflowY === 'auto' || overflowY === 'scroll' ||
           overflowX === 'auto' || overflowX === 'scroll') &&
          element !== viewport
        ) {
          // Check if this element can actually scroll in the wheel direction
          const canScrollY = element.scrollHeight > element.clientHeight;
          const canScrollX = element.scrollWidth > element.clientWidth;

          if ((e.deltaY !== 0 && canScrollY) || (e.deltaX !== 0 && canScrollX)) {
            return; // Let the nested scroller handle it
          }
        }

        element = element.parentElement as HTMLElement;
      }

      // No nested scroller found, scroll the main viewport
      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;
        }
      }
      onScrollPositionChange({ x: 0, y: 0 });
    }, 10);

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


  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={onScrollPositionChange}
        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;
