import React, { isValidElement, cloneElement, Children, Component, CSSProperties, ReactElement, ReactNode } from 'react';
import { findDOMNode } from 'react-dom';
import flatMap from 'lodash/flatMap';
import classNames from 'classnames';
import Col, { ColProps } from '../grid/col';
import warning from '../_util/warning';
import { FIELD_DATA_PROP, FIELD_META_PROP } from './constants';
import PureRenderMixin from '../rc-components/util/PureRenderMixin';
import Animate from '../animate';
import Row from '../grid/row';
import { FormItemValidateStatus } from './enum';
import FormContext, { FormContextValue } from './FormContext';

function intersperse(arr: Array<any>, inter: any) {
  return flatMap(arr, (a, i) => i ? [inter, a] : [a]);
}

export interface FormItemProps {
  prefixCls?: string;
  rowPrefixCls?: string;
  colPrefixCls?: string;
  className?: string;
  id?: string;
  label?: ReactNode;
  labelCol?: ColProps;
  wrapperCol?: ColProps;
  help?: ReactNode;
  extra?: ReactNode;
  validateStatus?: FormItemValidateStatus;
  hasFeedback?: boolean;
  required?: boolean;
  style?: CSSProperties;
  colon?: boolean;
  labelLayout?: 'float' | 'none' | 'horizontal';
  helpTransitionName?: string;
}

export default class FormItem extends Component<FormItemProps, any> {
  static displayName = 'FormItem';

  static defaultProps = {
    hasFeedback: false,
    colon: true,
    labelLayout: 'float',
  };

  static __FORM_ITEM = true;

  static get contextType(): typeof FormContext {
    return FormContext;
  }

  context: FormContextValue;

  helpShow = false;

  componentDidMount() {
    const { children } = this.props;
    warning(
      this.getControls(children, true).length <= 1,
      '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
      'while there are more than one `getFieldDecorator` in it.',
    );
  }

  shouldComponentUpdate(...args: any[]) {
    return PureRenderMixin.shouldComponentUpdate.apply(this, args);
  }

  getHelpMsg() {
    const { help } = this.props;
    if (help === undefined && this.getOnlyControl()) {
      const errors = this.getField().errors;
      if (errors) {
        return intersperse(errors.map((e: any, index: number) => (
          isValidElement(e.message)
            ? cloneElement(e.message, { key: index })
            : e.message
        )), ' ');
      }
      return '';
    }

    return help;
  }

  getControls(children: ReactNode, recursively: boolean) {
    let controls: ReactElement<any>[] = [];
    const childrenArray = Children.toArray(children);
    for (let i = 0; i < childrenArray.length; i++) {
      if (!recursively && controls.length > 0) {
        break;
      }

      const child = childrenArray[i] as ReactElement<any>;
      if (
        child.type &&
        ((child.type as any) === FormItem || (child.type as any).displayName === 'FormItem' || (child.type as any).__FORM_ITEM)
      ) {
        continue;
      }
      if (!child.props) {
        continue;
      }
      if (FIELD_META_PROP in child.props) {
        // And means FIELD_DATA_PROP in chidl.props, too.
        controls.push(child);
      } else if (child.props.children) {
        controls = controls.concat(this.getControls(child.props.children, recursively));
      }
    }
    return controls;
  }

  getOnlyControl() {
    const { children } = this.props;
    const child = this.getControls(children, false)[0];
    return child !== undefined ? child : null;
  }

  getChildProp(prop: string) {
    const child = this.getOnlyControl() as ReactElement<any>;
    return child && child.props && child.props[prop];
  }

  getId() {
    return this.getChildProp('id');
  }

  getMeta() {
    return this.getChildProp(FIELD_META_PROP);
  }

  getField() {
    return this.getChildProp(FIELD_DATA_PROP);
  }

  getPrefixCls() {
    const { prefixCls } = this.props;
    const { getPrefixCls } = this.context;
    return getPrefixCls('form', prefixCls);
  }

  onHelpAnimEnd = (_key: string, helpShow: boolean) => {
    this.helpShow = helpShow;
    if (!helpShow) {
      this.setState({});
    }
  };

  renderHelp() {
    const { helpTransitionName = 'show-error' } = this.props;
    const prefixCls = this.getPrefixCls();
    const help = this.getHelpMsg();
    const children = help ? (
      <div className={`${prefixCls}-explain`} key="help">
        {help}
      </div>
    ) : null;
    if (children) {
      this.helpShow = !!children;
    }
    return (
      <Animate
        transitionName={helpTransitionName}
        component=""
        transitionAppear
        key="help"
        onEnd={this.onHelpAnimEnd}
      >
        {children}
      </Animate>
    );
  }

  renderExtra() {
    const { extra } = this.props;
    const prefixCls = this.getPrefixCls();
    return extra ? <div className={`${prefixCls}-extra`}>{extra}</div> : null;
  }

  getValidateStatus(): FormItemValidateStatus | undefined {
    const onlyControl = this.getOnlyControl();
    if (onlyControl) {
      const field = this.getField();
      if (field.validating) {
        return FormItemValidateStatus.validating;
      }
      if (field.errors) {
        return FormItemValidateStatus.error;
      }
      const fieldValue = 'value' in field ? field.value : this.getMeta().initialValue;
      if (fieldValue !== undefined && fieldValue !== null && fieldValue !== '') {
        return FormItemValidateStatus.success;
      }
    }
  }

  renderValidateWrapper(c1: ReactNode, c2: ReactNode, c3: ReactNode) {
    const props = this.props;
    const prefixCls = this.getPrefixCls();
    const onlyControl = this.getOnlyControl();
    const validateStatus =
      props.validateStatus === undefined && onlyControl
        ? this.getValidateStatus()
        : props.validateStatus;

    let classes = `${prefixCls}-item-control`;
    if (validateStatus) {
      classes = classNames(`${prefixCls}-item-control`, {
        'has-feedback': props.hasFeedback || validateStatus === FormItemValidateStatus.validating,
        'has-success': validateStatus === FormItemValidateStatus.success,
        'has-warning': validateStatus === FormItemValidateStatus.warning,
        'has-error': validateStatus === FormItemValidateStatus.error,
        'is-validating': validateStatus === FormItemValidateStatus.validating,
      });
    }

    // 必输字段,输入框加黄色背景, 解决表格行内编辑,没有label的情况下没有提示必输标示的问题
    const required = this.isRequired();
    if (required) {
      classes = classNames(classes, [`${prefixCls}-item-required`]);
    }

    return (
      <div className={classes}>
        <span className={`${prefixCls}-item-children`}>{c1}</span>
        {c2}
        {c3}
      </div>
    );
  }

  renderWrapper(children: ReactNode) {
    const { wrapperCol, labelLayout, colPrefixCls } = this.props;
    const prefixCls = this.getPrefixCls();
    const required = this.isRequired();
    const isHorizontal = labelLayout === 'horizontal';
    const className = classNames(
      `${prefixCls}-item-control-wrapper`,
      wrapperCol && wrapperCol.className,
      {
        'is-required': isHorizontal ? undefined : required,
      },
    );
    return isHorizontal ? (
      <Col prefixCls={colPrefixCls} {...wrapperCol} className={className} key="wrapper">
        {children}
      </Col>
    ) : (
      <div className={className} key="wrapper">
        {children}
      </div>
    );
  }

  isRequired() {
    const { required } = this.props;
    if (required !== undefined) {
      return required;
    }
    if (this.getOnlyControl()) {
      const meta = this.getMeta() || {};
      const validate = meta.validate || [];

      return validate
        .filter((item: any) => !!item.rules)
        .some((item: any) => {
          return item.rules.some((rule: any) => rule.required);
        });
    }
    return false;
  }

  // Resolve duplicated ids bug between different forms

  onLabelClick = (e: any) => {
    const { label, id: propId } = this.props;
    const id = propId || this.getId();
    if (!id) {
      return;
    }
    const controls = document.querySelectorAll(`[id="${id}"]`);
    if (controls.length !== 1) {
      // Only prevent in default situation
      // Avoid preventing event in `label={<a href="xx">link</a>}``
      if (typeof label === 'string') {
        e.preventDefault();
      }
      const control = (findDOMNode(this) as HTMLElement).querySelector(
        `[id="${id}"]`,
      ) as HTMLElement;
      if (control && control.focus) {
        control.focus();
      }
    }
  };

  renderLabel() {
    const { prefixCls, label, labelCol, colon, id, colPrefixCls } = this.props;
    const context = this.context;
    const required = this.isRequired();

    const labelColClassName = classNames(
      `${prefixCls}-item-label`,
      labelCol && labelCol.className,
    );
    const labelClassName = classNames({
      [`${prefixCls}-item-required`]: required,
    });

    let labelChildren = label;
    // Keep label is original where there should have no colon
    const haveColon = colon && !context.vertical;
    // Remove duplicated user input colon
    if (haveColon && typeof label === 'string' && (label as string).trim() !== '') {
      labelChildren = (label as string).replace(/[:|:]\s*$/, '');
    }

    return label ? (
      <Col prefixCls={colPrefixCls} {...labelCol} className={labelColClassName} key="label">
        <label
          htmlFor={id || this.getId()}
          className={labelClassName}
          title={typeof label === 'string' ? label : ''}
          onClick={this.onLabelClick}
        >
          {labelChildren}
        </label>
      </Col>
    ) : null;
  }

  renderChildren() {
    const { children, labelLayout } = this.props;
    return [
      labelLayout === 'horizontal' && this.renderLabel(),
      this.renderWrapper(
        this.renderValidateWrapper(children, this.renderHelp(), this.renderExtra()),
      ),
    ];
  }

  renderFormItem(children: ReactNode) {
    const props = this.props;
    const prefixCls = this.getPrefixCls();
    const style = props.style;
    const itemClassName = {
      [`${prefixCls}-item`]: true,
      [`${prefixCls}-item-with-help`]: this.helpShow,
      [`${prefixCls}-item-no-colon`]: !props.colon,
      [`${props.className}`]: !!props.className,
    };

    return props.labelLayout === 'horizontal' ? (
      <Row prefixCls={props.rowPrefixCls} className={classNames(itemClassName)} style={style}>
        {children}
      </Row>
    ) : (
      <div className={classNames(itemClassName)} style={style}>
        {children}
      </div>
    );
  }

  render() {
    const children = this.renderChildren();
    return this.renderFormItem(children);
  }
}