import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import addEventListener from 'rc-util/lib/Dom/addEventListener';

import Spin from '../spin';
import Icon from '../icon';
import Pagination from '../pagination';
import Button from '../button';
import { Row } from '../grid';
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';

import Item from './Item';

export interface ListGridType {
  gutter?: number;
  column?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
  xs?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
  sm?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
  md?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
  lg?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
  xl?: 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24;
}

export interface InfinitePropType {
  onLoad?: any;
  loading?: boolean;
  offset?: number;
}

export interface ListProps {
  bordered?: boolean;
  className?: string;
  children?: React.ReactNode;
  extra?: React.ReactNode;
  id?: string;
  itemLayout: string;
  loading?: boolean;
  showLoadMore?: boolean;
  loadingMore?: boolean;
  onLoadMore?: React.FormEventHandler<any>;
  pagination?: any;
  prefixCls?: string;
  grid?: ListGridType;
  infinite?: InfinitePropType;
}

function getDefaultTarget() {
  return typeof window !== 'undefined' ?
    window : null;
}

function getOffsetTop(element?: HTMLElement): number {
  if (!element) {
    return 0;
  }

  if (!element.getClientRects().length) {
    return 0;
  }

  const rect = element.getBoundingClientRect();

  if (rect.width || rect.height) {
    const doc = element.ownerDocument;
    const docElem = doc.documentElement;
    return rect.top - docElem.clientTop;
  }

  return rect.top;
}

function getViewportHeight() {
  const win = getDefaultTarget();
  if (!win) {
    return 0;
  }
  return Math.max(win.document.documentElement.clientHeight, win.innerHeight || 0);
}

export default class List extends Component<ListProps> {
  static Item: typeof Item = Item;

  static childContextTypes = {
    grid: PropTypes.any,
  };

  scrollEvent: any;
  resizeEvent: any;
  timeout: any;

  events = [
    'resize',
    'scroll',
    'touchstart',
    'touchmove',
    'touchend',
    'pageshow',
    'load',
  ];
  infiniteLoaded = false;
  eventHandlers = {};

  private node: any;

  getChildContext() {
    return {
      grid: this.props.grid,
    };
  }

  componentDidMount() {
    if (!this.isInfinite()) {
      return;
    }
    const target = getDefaultTarget;

    // Wait for parent component ref has its value
    this.timeout = setTimeout(() => {
      this.setTargetEventListeners(target);
    });
  }

  componentWillUnmount() {
    if (!this.isInfinite()) {
      return;
    }
    this.clearEventListeners();
    clearTimeout(this.timeout);
    (this.infiniteLoad as any).cancel();
  }

  getNode = (n) => {
    this.node = n;
  }

  isInfinite() {
    const { infinite } = this.props;
    return infinite && (typeof infinite.onLoad === 'function');
  }

  setTargetEventListeners(getTarget) {
    const target = getTarget();
    if (!target) {
      return;
    }
    this.clearEventListeners();

    this.events.forEach(eventName => {
      this.eventHandlers[eventName] = addEventListener(target, eventName, this.infiniteLoad);
    });
  }

  clearEventListeners() {
    this.events.forEach(eventName => {
      const handler = this.eventHandlers[eventName];
      if (handler && handler.remove) {
        handler.remove();
      }
    });
  }

  @throttleByAnimationFrameDecorator()
  infiniteLoad() {
    const { infinite = {} } = this.props;
    const targetElement = this.node;

    if (this.infiniteLoaded || !targetElement) {
      return;
    }

    const viewportHeight = getViewportHeight();
    const eleOffsetTop = getOffsetTop(targetElement);
    const bottomPositionY = eleOffsetTop + targetElement.offsetHeight - viewportHeight + (infinite.offset || 0);

    if (bottomPositionY < 0) {
      this.infiniteLoaded = true;
      infinite.onLoad(() => {
        this.infiniteLoaded = false;
      });
    }
  }

  render() {
    const {
      bordered = true,
      className,
      children,
      loading = false,
      itemLayout,
      showLoadMore = false,
      loadingMore = false,
      onLoadMore = (() => {
      }),
      pagination = false,
      prefixCls = 'ant-list',
      grid,
      infinite = {},
      ...rest,
    } = this.props;

    const classString = classNames(prefixCls, className, {
      [`${prefixCls}-vertical`]: itemLayout === 'vertical',
      [`${prefixCls}-bordered`]: bordered,
      [`${prefixCls}-loading`]: loading,
      [`${prefixCls}-grid`]: grid,
      [`${prefixCls}-infinite`]: infinite.onLoad,
    });

    const moreButton = (
      <Button onClick={onLoadMore}>
        <Icon type="loading"/>
        加载中...
      </Button>
    );

    const moreContent = (
      <div className={`${prefixCls}-more`}>
        {loadingMore ? moreButton : <Button onClick={onLoadMore}>加载更多...</Button>}
      </div>
    );

    const paginationContent = (
      <div className={`${prefixCls}-pagination`}>
        <Pagination {...pagination} />
      </div>
    );

    const childrenContent = grid ? (
      <Row gutter={grid.gutter}>{children}</Row>
    ) : children;

    const content = loading ? (
      <Spin>
        {childrenContent}
        {showLoadMore && moreContent}
        {(!showLoadMore && pagination) && paginationContent}
      </Spin>
      ) : (
      <div>
        {childrenContent}
        {showLoadMore && moreContent}
        {(!showLoadMore && pagination) && paginationContent}
        {infinite.loading && (<div className={`${prefixCls}-spin`}><Spin /></div>)}
      </div>
    );

    return (
      <div className={classString} {...rest} ref={this.getNode}>
        {content}
      </div>
    );
  }
}
