import React, { useRef, useState, useCallback, useEffect } from 'react';
import {
  VariableSizeList,
  ListChildComponentProps,
  ListOnScrollProps,
} from 'react-window';
import AutoSizer, { Size } from 'react-virtualized-auto-sizer';
import { Icon } from 'antd';
import { formatMessage } from 'umi-plugin-locale';

import styles from './style.less';
import { connect } from 'dva';

const listPosition: { [key: string]: number } = {};

type ActionCallback<T = any> = () => T | Promise<T>;

export interface ListProps<T = any> {
  className?: string;
  distance?: number;
  refreshDistance?: number;
  loadDistance?: number;
  itemSize?: number;
  pullText?: string;
  releaseText?: string;
  refreshText?: string;
  loadingText?: string;
  loadFinishedText?: string;
  refreshing?: boolean;
  enableRefresh?: boolean;
  loading?: boolean;
  enableLoading?: boolean;
  data?: T[] | null;
  onRefresh?: ActionCallback;
  onLoadMore?: ActionCallback;
  renderItem?: (item: T, index: number, array: T[]) => React.ReactNode;
  itemKey?: (data: T) => string;
}

interface Point {
  x: number;
  y: number;
}

const List: React.FC<ListProps> = ({
  className = '',
  distance = 120,
  refreshDistance = 80,
  loadDistance = 200,
  itemSize = 80,
  pullText = 'common.pullToRefresh',
  releaseText = 'common.releaseToRefresh',
  refreshText = 'common.refreshing',
  loadingText = 'common.loading',
  loadFinishedText = 'common.loadFinished',
  refreshing = false,
  loading = false,
  enableRefresh = true,
  enableLoading = true,
  data = [],
  renderItem,
  itemKey,
  onRefresh,
  onLoadMore,
  ...props
}) => {
  if (refreshDistance > distance) {
    throw Error('refreshDistance 应该小于 distance');
  }

  const scrollOffset = useRef(0);
  const touchEnable = useRef(false);
  const startPoint = useRef<Point>();
  const scrollContainer = useRef<VariableSizeList>();
  const [pullOffset, setPullOffset] = useState(0);
  const validToRefresh = pullOffset >= refreshDistance;
  const key = (props as any).location.pathname;

  useEffect(() => {
    if (refreshing && loading) {
      throw Error('refreshing 与 loading 不能同时进行');
    }
    if (refreshing === loading) { // 同时为 false
      setPullOffset(0);
    } else {
      setPullOffset(refreshing ? refreshDistance : 0);
    }
  }, [refreshing, refreshDistance, loading]);

  useEffect(() => {
    const { current } = scrollContainer;
    if (current) {
      current.resetAfterIndex(0, false);
    }
    return () => { listPosition[key] = scrollOffset.current; }
  }, [data.length]);

  const attachContainer = useCallback((node: VariableSizeList) => {
    if (node) {
      scrollContainer.current = node;
      const position = listPosition[key];
      if (typeof position === 'number') {
        node.scrollTo(position);
      }
    }
  }, []);

  const onScroll = useCallback((e: ListOnScrollProps) => {
    const { current } = scrollContainer;
    scrollOffset.current = e.scrollOffset;
    if (
      enableLoading && !loading && !refreshing && current &&
      data.length * itemSize - (+current.props.height) - e.scrollOffset < loadDistance
    ) {
      onLoadMore && onLoadMore();
    }
  }, [loading, data.length, itemSize, enableLoading, key]);

  const onTouchStart = useCallback((e: React.TouchEvent) => {
    if (!enableRefresh || refreshing || loading) return;
    const touch = e.touches[0];
    if (!touch || scrollOffset.current) return;
    startPoint.current = { x: touch.clientX, y: touch.clientY };
  }, [enableRefresh, refreshing, loading]);

  const onTouchEnd = useCallback(() => {
    if (!enableRefresh || refreshing || loading) return;
    touchEnable.current = false;
    startPoint.current = undefined;
    if (validToRefresh) {
      onRefresh && onRefresh();
    } else {
      if (pullOffset !== 0) {
        setPullOffset(0);
      }
    }
  }, [pullOffset, validToRefresh, refreshing, enableRefresh, loading]);

  const onTouchMove = useCallback((e: React.TouchEvent) => {
    if (!enableRefresh || refreshing) return;

    const touch = e.touches[0];
    const start = startPoint.current;
    if (!touch || !start) return;
    if (!touchEnable.current) {
      const deltaX = Math.abs(touch.clientX - start.x);
      const deltaY = Math.abs(touch.clientY - start.y);
      const yDirection = deltaY !== 0 && deltaX / deltaY < 0.5;
      const enable = yDirection && scrollOffset.current === 0;
      touchEnable.current = enable;
      if (!enable) {
        onTouchEnd();
      }
      return;
    }

    if (touchEnable.current && e.touches[0]) {
      const { clientY } = e.touches[0];
      const offset = clientY - start.y;
      if (offset > 0) {
        const variable = offset / distance;
        if (variable <= Math.PI / 2) {
          const result = distance * Math.sin(offset / distance);
          setPullOffset(result);
        }
      }
    }
  }, [enableRefresh, distance]);

  // 每一行的渲染
  const Row: React.FC<ListChildComponentProps> = useCallback(({ style, index }) => {
    let child;
    if (index === data.length) {
      child = (
        <div className={styles.loadcontent}>
          {loading && <Icon className="logo" type="loading" spin={true} />}
          <span>
            {formatMessage({ id: loading ? loadingText : loadFinishedText })}
          </span>
        </div>
      );
    } else {
      child = renderItem && renderItem(data[index], index, data);
    }
    return (
      <div className={styles.item} style={style}>
        {child}
      </div>
    );
  }, [data, loading]);

  const calcItemSize = useCallback((index: number) => {
    if (!refreshing && index === data.length) return 40;
    return itemSize;
  }, [data, refreshing]);

  const keyGenerator = useCallback((index: number) => {
    if (itemKey) {
      const item = data[index];
      return item ? itemKey(item) : 'loader';
    }
    return `${index}`;
  }, [data, itemKey]);

  // 列表内容
  const ActualList: React.FC<Size> = useCallback(({ width, height }) => (
    <VariableSizeList
      ref={attachContainer}
      style={{
        transform: `translateY(${pullOffset}px)`,
        transition: pullOffset === 0 || refreshing || loading ? 'all .2s' : undefined,
        touchAction: refreshing ? 'none' : 'unset',
      }}
      className={styles.listcontent}
      itemCount={data.length + (refreshing ? 0 : 1)}
      itemSize={calcItemSize}
      height={height}
      width={width}
      onScroll={onScroll}
      itemKey={keyGenerator}
    >
      {Row}
    </VariableSizeList>
  ), [pullOffset, data, itemSize, Row, refreshing]);

  let pullContent = validToRefresh ? releaseText : pullText;
  if (refreshing) {
    pullContent = refreshText;
  }
  const RefreshContent = (
    <div
      style={{
        transform: `translateY(${pullOffset / 3}px)`,
        transition: pullOffset === 0 || refreshing ? 'all .2s' : undefined,
      }}
      className={styles.pullcontent}
    >
      {refreshing ? (
        <Icon type="loading" className="loading" spin={true} />
      ) : (
          <Icon
            type="arrow-down"
            className={`logo ${validToRefresh ? 'active' : ''}`}
          />
        )}
      <span className="desc">{formatMessage({ id: pullContent })}</span>
    </div>
  );

  return (
    <div
      className={`${styles.container} ${className}`}
      onTouchStart={onTouchStart}
      onTouchMove={onTouchMove}
      onTouchEnd={onTouchEnd}
    >
      {RefreshContent}
      <AutoSizer>
        {ActualList}
      </AutoSizer>
    </div>
  );
}

export default connect(
  ({ router } : any) => ({ ...router }),
)(List);
