import React, { CSSProperties, Key, PureComponent, ReactNode } from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import classNames from 'classnames';
import { NotificationManager } from 'choerodon-ui/shared';
import noop from 'lodash/noop';
import debounce from 'lodash/debounce';
import scrollIntoView from 'scroll-into-view-if-needed';
import { NotificationInterface } from 'choerodon-ui/shared/notification-manager';
import Animate from '../animate';
import Notice, { NoticeProps } from './Notice';
import { getNoticeLocale } from './locale';
import EventManager from '../_util/EventManager';
import { getStyle } from '../rc-components/util/Dom/css';

export function newNotificationInstance(properties: NotificationProps & { getContainer?: (() => HTMLElement) | undefined }, callback: (api: NotificationInterface) => void) {
  const { getContainer, ...props } = properties || {};
  const div = document.createElement('div');
  if (getContainer) {
    const root = getContainer();
    root.appendChild(div);
  } else {
    document.body.appendChild(div);
  }
  let called = false;

  function ref(notification) {
    if (called) {
      return;
    }
    called = true;
    callback({
      notice(noticeProps) {
        notification.add(noticeProps);
      },
      removeNotice(key) {
        notification.remove(key);
      },
      destroy() {
        unmountComponentAtNode(div);
        const { parentNode } = div;
        if (parentNode) {
          parentNode.removeChild(div);
        }
      },
    });
  }

  render(<Notification {...props} ref={ref} />, div);
}

export interface NotificationProps {
  prefixCls?: string;
  className?: string;
  transitionName?: string;
  animation?: string;
  style?: CSSProperties;
  contentClassName?: string;
  closeIcon?: ReactNode;
  maxCount?: number;
  foldCount?: number;
}

export interface NotificationState {
  notices: NoticeProps[];
  scrollHeight: string | number;
  totalHeight: number;
  offset: number;
}

export default class Notification extends PureComponent<NotificationProps, NotificationState> {
  static defaultProps = {
    prefixCls: 'c7n-notification',
    animation: 'fade',
    style: {
      top: 65,
      left: '50%',
    },
  };

  static newInstance = newNotificationInstance;

  scrollRef: HTMLDivElement | null = null;

  scrollEvent?: any;

  isRemove: boolean;

  state: NotificationState = {
    notices: [],
    scrollHeight: 'auto',
    totalHeight: 0,
    offset: 0,
  };

  dispose() {
    const { scrollEvent } = this;
    if (scrollEvent) {
      scrollEvent.clear();
    }
  }

  componentWillUnmount() {
    this.dispose();
  }

  getTransitionName() {
    const { transitionName, animation, prefixCls } = this.props;
    if (!transitionName && animation) {
      return `${prefixCls}-${animation}`;
    }
    return transitionName;
  }

  onAnimateEnd = () => {
    const { notices } = this.state;
    const { foldCount } = this.props;
    if (foldCount) {
      const { scrollRef } = this;
      if (scrollRef) {
        const childSpan = scrollRef.firstChild;
        if (!childSpan) return;
        const childNodes = childSpan.childNodes;
        const lastNode = childNodes[childNodes.length - 1] as HTMLDivElement;

        if (childNodes.length > foldCount && notices.length > foldCount) {
          let totalHeight = 0;
          for (let i = 0; i < childNodes.length; i += 1) {
            const element = childNodes[i] as HTMLDivElement;
            totalHeight += element.offsetHeight + getStyle(element, 'margin-bottom');
          }
          const scrollHeight = (totalHeight / childNodes.length) * (foldCount + 0.5);
          this.setState(
            {
              scrollHeight,
              totalHeight,
            }, () => {
              if (!this.isRemove) {
                scrollIntoView(lastNode, {
                  block: 'center',
                  behavior: 'smooth',
                  scrollMode: 'if-needed',
                  boundary: scrollRef,
                });
              }
            });
        } else {
          this.setState({
            scrollHeight: 'auto',
          });
        }
      }
    }
  };

  add(notice: NoticeProps) {
    if (!notice.key) {
      notice.key = NotificationManager.getUuid();
    }
    const { key } = notice;
    const { maxCount } = this.props;
    this.setState(previousState => {
      const notices = previousState.notices;
      if (!notices.filter(v => v.key === key).length) {
        if (maxCount && notices && notices.length > 0 && notices.length >= maxCount) {
          notices.shift();
        }
        this.isRemove = false;
        return {
          ...previousState,
          notices: notices.concat(notice),
        };
      }
    });
  }

  remove(key: Key) {
    this.setState(previousState => {
      this.isRemove = true;
      const notices = previousState.notices.filter(notice => notice.key !== key);
      return {
        notices,
      };
    });
  }

  clearNotices = (): void => {
    this.setState({
      notices: [],
    });
  };

  handleNoticeClose = (eventKey): void => {
    const { notices } = this.state;
    const notice = notices.find(({ key }) => key === eventKey);
    this.remove(eventKey);
    if (notice) {
      const { onClose = noop } = notice;
      onClose(eventKey);
    }
  };

  saveScrollRef = (dom) => {
    this.scrollRef = dom;
    if (dom) {
      const debouncedResize = debounce((e) => {
        this.setState({
          offset: e.target.scrollTop,
        });
      }, 200);
      this.scrollEvent = new EventManager(dom).addEventListener('scroll', debouncedResize);
    } else {
      this.dispose();
    }
  };

  render() {
    const { notices, scrollHeight, offset, totalHeight } = this.state;
    const { contentClassName, prefixCls, closeIcon, className, style, foldCount } = this.props;
    const noticeNodes = notices.map((notice) => (
      <Notice
        prefixCls={prefixCls}
        contentClassName={contentClassName}
        {...notice}
        onClose={this.handleNoticeClose}
        closeIcon={closeIcon}
        key={notice.key}
        eventKey={notice.key}
        foldable={!!foldCount}
        offset={offset}
        scrollHeight={scrollHeight}
        totalHeight={totalHeight}
      />
    ));
    const cls = classNames(`${prefixCls}`, className, [{
      [`${prefixCls}-before-shadow`]: !!foldCount && notices.length > foldCount && offset > 0,
      [`${prefixCls}-after-shadow`]:
      foldCount && notices.length > foldCount &&
      totalHeight - (typeof scrollHeight === 'number' ? scrollHeight : 0) - offset > 15,
    }]);

    const scrollCls = classNames({
      [`${prefixCls}-scroll`]: !!foldCount && scrollHeight !== 'auto',
    });

    const runtimeLocale = getNoticeLocale();

    return (
      <div className={cls} style={style}>
        <div
          className={scrollCls}
          style={foldCount ? {
            height: scrollHeight,
          } : undefined}
          ref={foldCount ? this.saveScrollRef : undefined}
        >
          <Animate onEnd={this.onAnimateEnd} transitionName={this.getTransitionName()}>
            {noticeNodes}
          </Animate>
        </div>

        {foldCount && notices.length > foldCount && (
          <div className={`${prefixCls}-alert`}>
            <div className={`${prefixCls}-alert-message`}>{`${runtimeLocale.total} ${notices.length} ${runtimeLocale.message}`}</div>
            <div className={`${prefixCls}-alert-close`} onClick={this.clearNotices}>{`${runtimeLocale.closeAll}`}</div>
          </div>
        )}
      </div>
    );
  }
}