import React from 'react';
import { Virtuoso, Components } from 'react-virtuoso';

import PullToRefresh from 'soapbox/components/pull-to-refresh';

import { Spinner, Text } from './ui';

type Context = {
  itemClassName?: string,
  listClassName?: string,
}

// NOTE: It's crucial to space lists with **padding** instead of margin!
// Pass an `itemClassName` like `pb-3`, NOT a `space-y-3` className
// https://virtuoso.dev/troubleshooting#list-does-not-scroll-to-the-bottom--items-jump-around
const Item: Components<Context>['Item'] = ({ context, ...rest }) => (
  <div className={context?.itemClassName} {...rest} />
);

// Ensure the className winds up here
const List: Components<Context>['List'] = React.forwardRef((props, ref) => {
  const { context, ...rest } = props;
  return <div ref={ref} className={context?.listClassName} {...rest} />;
});

interface IScrollableList {
  scrollKey?: string,
  onLoadMore?: () => void,
  isLoading?: boolean,
  showLoading?: boolean,
  hasMore?: boolean,
  prepend?: React.ReactElement,
  alwaysPrepend?: boolean,
  emptyMessage?: React.ReactNode,
  children: Iterable<React.ReactNode>,
  onScrollToTop?: () => void,
  onScroll?: () => void,
  placeholderComponent?: React.ComponentType,
  placeholderCount?: number,
  onRefresh?: () => Promise<any>,
  className?: string,
  itemClassName?: string,
}

/** Legacy ScrollableList with Virtuoso for backwards-compatibility */
const ScrollableList: React.FC<IScrollableList> = ({
  prepend = null,
  alwaysPrepend,
  children,
  isLoading,
  emptyMessage,
  showLoading,
  onRefresh,
  onScroll,
  onScrollToTop,
  onLoadMore,
  className,
  itemClassName,
  hasMore,
  placeholderComponent: Placeholder,
  placeholderCount = 0,
}) => {
  /** Normalized children */
  const elements = Array.from(children || []);

  const showPlaceholder = showLoading && Placeholder && placeholderCount > 0;

  // NOTE: We are doing some trickery to load a feed of placeholders
  // Virtuoso's `EmptyPlaceholder` unfortunately doesn't work for our use-case
  const data = showPlaceholder ? Array(placeholderCount).fill('') : elements;
  const isEmpty = data.length === 0; // Yes, if it has placeholders it isn't "empty"

  // Add a placeholder at the bottom for loading
  // (Don't use Virtuoso's `Footer` component because it doesn't preserve its height)
  if (hasMore && Placeholder) {
    data.push(<Placeholder />);
  } else if (hasMore) {
    data.push(<Spinner />);
  }

  /* Render an empty state instead of the scrollable list */
  const renderEmpty = (): JSX.Element => {
    return (
      <div className='mt-2'>
        {alwaysPrepend && prepend}

        <div className='bg-primary-50 dark:bg-slate-700 mt-2 rounded-lg text-center p-8'>
          {isLoading ? (
            <Spinner />
          ) : (
            <Text>{emptyMessage}</Text>
          )}
        </div>
      </div>
    );
  };

  /** Render a single item */
  const renderItem = (_i: number, element: JSX.Element): JSX.Element => {
    if (showPlaceholder) {
      return <Placeholder />;
    } else {
      return element;
    }
  };

  /** Render the actual Virtuoso list */
  const renderFeed = (): JSX.Element => (
    <Virtuoso
      useWindowScroll
      className={className}
      data={data}
      startReached={onScrollToTop}
      endReached={onLoadMore}
      isScrolling={isScrolling => isScrolling && onScroll && onScroll()}
      itemContent={renderItem}
      context={{
        listClassName: className,
        itemClassName,
      }}
      components={{
        Header: () => prepend,
        ScrollSeekPlaceholder: Placeholder as any,
        EmptyPlaceholder: () => renderEmpty(),
        List,
        Item,
      }}
    />
  );

  /** Conditionally render inner elements */
  const renderBody = (): JSX.Element => {
    if (isEmpty) {
      return renderEmpty();
    } else {
      return renderFeed();
    }
  };

  return (
    <PullToRefresh onRefresh={onRefresh}>
      {renderBody()}
    </PullToRefresh>
  );
};

export default ScrollableList;
