import {useCallback, useEffect, useRef} from 'react'

import {Virtuoso, VirtuosoProps} from 'react-virtuoso'

import {RefreshIndicator} from '../../internal/components/refresh-indicator'
import {usePullToRefresh} from '../../internal/usePullToRefresh'
import {findVirtuosoScrollableElement} from '../../internal/utils/virtuoso-dom'
import {cn} from '../../lib/utils'
import '../../styles/utilities.css'

import {Pagination} from './pagination'

const DEFAULT_REFRESH_PULL_THRESHOLD = 200
const ELEMENT_BIND_DELAY = 100

interface Props<T = any>
  extends Omit<
    VirtuosoProps<T, unknown>,
    'data' | 'itemContent' | 'endReached'
  > {
  items: T[]
  renderItem: (item: T, index: number) => React.ReactNode
  showScrollbar?: boolean
  header?: React.ReactNode
  fetchMore?: () => Promise<void>
  loadingComponent?: React.ReactNode
  isFetchingMore?: boolean
  onRefresh?: () => Promise<void>
  refreshing?: boolean
  enablePullToRefresh?: boolean
}

export function List<T = any>({
  items,
  height,
  renderItem,
  className,
  showScrollbar = false,
  header,
  fetchMore,
  loadingComponent,
  isFetchingMore,
  onRefresh,
  refreshing,
  enablePullToRefresh = true,
  ...virtuosoProps
}: Props<T>) {
  const inFlightFetchMoreRef = useRef<Promise<void> | null>(null)
  const virtuosoRef = useRef<any>(null)
  const containerRef = useRef<HTMLDivElement>(null)

  const {state: pullToRefreshState, bindToElement} = usePullToRefresh({
    onRefresh,
    threshold: DEFAULT_REFRESH_PULL_THRESHOLD,
    enabled: enablePullToRefresh && Boolean(onRefresh),
  })

  const _fetchMore = useCallback(() => {
    // Dedupe concurrent calls by returning the same in-flight promise
    if (inFlightFetchMoreRef.current) return

    const current = Promise.resolve(fetchMore?.()).finally(() => {
      // Only clear if this is still the most recent promise
      if (inFlightFetchMoreRef.current === current) {
        inFlightFetchMoreRef.current = null
      }
    })

    inFlightFetchMoreRef.current = current
  }, [fetchMore])

  const itemContent = useCallback(
    (index: number, item: T) => <>{renderItem(item, index)}</>,
    [renderItem]
  )

  const Footer = useCallback(() => {
    if (!fetchMore) return null

    return (
      <Pagination
        fetchMore={_fetchMore}
        loadingComponent={loadingComponent}
        isFetchingMore={isFetchingMore}
      />
    )
  }, [_fetchMore, fetchMore, loadingComponent, isFetchingMore])

  const classNames = cn(showScrollbar ? undefined : 'no-scrollbars', className)

  useEffect(() => {
    if (containerRef.current && enablePullToRefresh && onRefresh) {
      let cleanup: (() => void) | undefined

      const findAndBind = () => {
        if (!containerRef.current) return

        const scrollableElement = findVirtuosoScrollableElement(
          containerRef.current
        )
        cleanup = bindToElement(scrollableElement)
      }

      const timeoutId = setTimeout(findAndBind, ELEMENT_BIND_DELAY)

      return () => {
        clearTimeout(timeoutId)
        if (cleanup) cleanup()
      }
    }
    return undefined
  }, [bindToElement, enablePullToRefresh, onRefresh])

  const EnhancedHeader = useCallback(() => {
    const effectivePullDistance = refreshing
      ? Math.max(pullToRefreshState.pullDistance, 140)
      : pullToRefreshState.pullDistance

    const refreshHeaderHeight = Math.min(
      Math.max(effectivePullDistance, 0),
      140
    )

    return (
      <>
        {enablePullToRefresh && onRefresh && (
          <div
            className="flex items-center justify-center"
            style={{
              height: refreshHeaderHeight,
              overflow: 'hidden',
            }}
          >
            <RefreshIndicator
              pullDistance={pullToRefreshState.pullDistance}
              threshold={DEFAULT_REFRESH_PULL_THRESHOLD}
              isRefreshing={refreshing ?? false}
              canRefresh={pullToRefreshState.canRefresh}
              className="relative top-0 inset-x-auto"
            />
          </div>
        )}
        {header && <div>{header}</div>}
      </>
    )
  }, [header, enablePullToRefresh, onRefresh, pullToRefreshState, refreshing])

  return (
    <div
      ref={containerRef}
      className={cn('relative transition-all duration-200', classNames)}
      style={{
        height,
      }}
    >
      <Virtuoso
        ref={virtuosoRef}
        className="h-full w-full"
        data={items}
        itemContent={itemContent}
        components={{
          Header: EnhancedHeader,
          Footer,
        }}
        endReached={fetchMore ? _fetchMore : undefined}
        {...virtuosoProps}
      />
    </div>
  )
}
