import { useCallback, useEffect, useRef } from 'react';
import { useI18n } from '@theorchard/suite-i18n-localization';

import type { BoxProps } from '~/src/components/Box';
import type { FC, ReactNode } from 'react';

import Box from '~/src/components/Box';
import Button from '~/src/components/Button';
import useIsInViewport from '~/src/components/IsInViewport/useIsInViewport';
import Loading from '~/src/components/Loading';
import RefreshIcon from '../Icon/RefreshIcon';

export interface InfiniteScrollProps extends BoxProps {
  children: ReactNode;
  isLoading?: boolean;
  hasError?: boolean;
  hasMore?: boolean;
  onLoadMore(): void;
  onRetry?(): void;
  threshold?: number | number[];
}

const SENTINEL_HEIGHT = '4rem';

export const InfiniteScroll: FC<InfiniteScrollProps> = ({
  testId,
  children,
  isLoading = false,
  hasError = false,
  hasMore = true,
  onLoadMore,
  onRetry,
  threshold = 0,
  ...boxProps
}) => {
  const { t } = useI18n('app');

  // State ref keeps handleViewportChange stable. If we put isLoading/hasError/
  // hasMore/onLoadMore in useCallback deps, every state change recreates the
  // callback → useDebounce inside useIsInViewport recreates the debounced fn →
  // the IntersectionObserver tears down and re-attaches → the fresh observer
  // immediately fires the *current* intersection state, double-firing onLoadMore
  // when the sentinel is still in view after a load completes.
  const stateRef = useRef({ isLoading, hasError, hasMore, onLoadMore });
  const sentinelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    stateRef.current = { isLoading, hasError, hasMore, onLoadMore };
  }, [isLoading, hasError, hasMore, onLoadMore]);

  const handleViewportChange = useCallback(
    ({ isInViewport }: { isInViewport: boolean }) => {
      if (!isInViewport) return;

      const { isLoading, hasError, hasMore, onLoadMore } = stateRef.current;
      if (isLoading || hasError || !hasMore) return;

      onLoadMore();
    },
    []
  );

  useIsInViewport({
    targetRef: sentinelRef,
    fallbackValue: false,
    threshold,
    skip: !hasMore || hasError,
    debounceMs: 0,
    onChange: handleViewportChange,
  });

  const handleRetry = useCallback(() => {
    (onRetry ?? onLoadMore)();
  }, [onRetry, onLoadMore]);

  const renderSentinel = () => {
    if (!hasMore && !hasError) return null;

    return (
      <Box
        nodeRef={sentinelRef}
        margin="2rem 0"
        height={SENTINEL_HEIGHT}
        centerContent
        testId={testId ? `${testId}-sentinel` : undefined}
      >
        {hasError ? (
          <Button
            Icon={RefreshIcon}
            text={t('actions.retry')}
            isInline
            height={SENTINEL_HEIGHT}
            onClick={handleRetry}
          />
        ) : (
          isLoading && (
            <Box centerContent height={SENTINEL_HEIGHT}>
              <Loading size="2.4rem" />
            </Box>
          )
        )}
      </Box>
    );
  };

  return (
    <Box testId={testId} {...boxProps}>
      {children}
      {renderSentinel()}
    </Box>
  );
};
