import React, { Children, cloneElement, FunctionComponent, isValidElement, ReactElement, ReactNode, useContext, useMemo, useRef } from 'react';
import { action, observable, ObservableMap } from 'mobx';
import { observer } from 'mobx-react-lite';
import Trigger from 'choerodon-ui/lib/trigger';
import { Action } from 'choerodon-ui/lib/trigger/enum';
import ConfigContext from 'choerodon-ui/lib/config-provider/ConfigContext';
import BUILT_IN_PLACEMENTS from '../trigger-field/placements';
import { ButtonColor, FuncType } from '../button/enum';
import Button, { ButtonProps } from '../button/Button';
import { $l } from '../locale-context';
import isFragment from '../_util/isFragment';
import Attachment, { AttachmentProps } from './Attachment';
import { iteratorReduce } from '../_util/iteratorUtils';

export interface AttachmentGroupProps extends ButtonProps {
  viewMode: 'list' | 'popup';
  label?: ReactNode;
  colSpan?: number;
  rowSpan?: number;
  text?: ReactNode;
  count?: number;
}

type GetRef = (attachment: Attachment | null, index: number) => void;

function getRefCallback(callback, index) {
  return item => callback(item, index);
}

function normalizeAttachments(children: ReactNode, getRef?: GetRef, index = { count: 0 }): ReactNode {
  return Children.map(children, (child) => {
    if (isFragment(child)) {
      return normalizeAttachments(child.props.children, getRef, index);
    }
    if (isValidElement<AttachmentProps>(child) && (child.type as any).__PRO_ATTACHMENT) {
      const props: AttachmentProps & { ref?: GetRef } = { viewMode: 'list', readOnly: true, __inGroup: true };
      if (getRef) {
        const { count } = index;
        props.ref = getRefCallback(getRef, count);
        index.count = count + 1;
      }
      return cloneElement<AttachmentProps>(child, props);
    }
    return undefined;
  });
}

const AttachmentGroup: FunctionComponent<AttachmentGroupProps> = function AttachmentGroup(props) {
  const { viewMode, children, hidden, text, count, ...buttonProps } = props;
  const hasCount = count !== undefined;
  const { getProPrefixCls, getConfig } = useContext(ConfigContext);
  const listRef = useRef<ObservableMap<number, Attachment>>(observable.map());
  const prefixCls = getProPrefixCls('attachment');
  const computedCount = hasCount ? count : iteratorReduce<Attachment, number>(listRef.current.values(), (sum, attachment) => sum + (attachment.count || 0), 0);
  const attachments = useMemo((): ReactElement | undefined => children ? (
    <div className={`${prefixCls}-group`}>
      {
        normalizeAttachments(children, hasCount ? undefined : action((attachment, index) => {
          if (attachment) {
            listRef.current.set(index, attachment);
          } else {
            listRef.current.delete(index);
          }
        }))
      }
    </div>
  ) : undefined, [children, hasCount, viewMode, prefixCls]);
  const renderEmpty = (): ReactElement | undefined => {
    if (computedCount === 0) {
      return (
        <div className={`${prefixCls}-empty`}>
          {getConfig('renderEmpty')('Attachment')}
        </div>
      );
    }
  };
  const content: ReactElement = (
    <>
      {renderEmpty()}
      {attachments}
    </>
  );
  const renderGroup = (): ReactElement | null => {
    if (hidden) {
      return null;
    }
    if (viewMode === 'list') {
      return content;
    }
    return (
      <Trigger
        prefixCls={prefixCls}
        popupContent={content}
        action={[Action.hover, Action.focus]}
        builtinPlacements={BUILT_IN_PLACEMENTS}
        popupPlacement="bottomLeft"
        forceRender={!hasCount}
      >
        <Button
          icon="attach_file"
          funcType={FuncType.link}
          color={ButtonColor.primary}
          {...buttonProps}
        >
          {text || $l('Attachment', 'view_attachment')} {computedCount || undefined}
        </Button>
      </Trigger>
    );
  };

  return renderGroup();
};

AttachmentGroup.defaultProps = {
  viewMode: 'popup',
};

AttachmentGroup.displayName = 'AttachmentGroup';

export default observer(AttachmentGroup);