import intersperse from 'intersperse';
import PropTypes from '../_util/vue-types';
import classNames from 'classnames';
import find from 'lodash/find';
import Row from '../grid/Row';
import Col, { ColProps } from '../grid/Col';
import warning from '../_util/warning';
import { FIELD_META_PROP, FIELD_DATA_PROP } from './constants';
import {
  initDefaultProps,
  getComponentFromProp,
  filterEmpty,
  getSlotOptions,
  isValidElement,
  getAllChildren,
} from '../_util/props-util';
import getTransitionProps from '../_util/getTransitionProps';
import BaseMixin from '../_util/BaseMixin';
import { cloneElement, cloneVNodes } from '../_util/vnode';
import Icon from '../icon';

export const FormItemProps = {
  id: PropTypes.string,
  prefixCls: PropTypes.string,
  label: PropTypes.any,
  labelCol: PropTypes.shape(ColProps).loose,
  wrapperCol: PropTypes.shape(ColProps).loose,
  help: PropTypes.any,
  extra: PropTypes.any,
  validateStatus: PropTypes.oneOf(['', 'success', 'warning', 'error', 'validating']),
  hasFeedback: PropTypes.bool,
  required: PropTypes.bool,
  colon: PropTypes.bool,
  fieldDecoratorId: PropTypes.string,
  fieldDecoratorOptions: PropTypes.object,
};

export default {
  name: 'AFormItem',
  __ANT_FORM_ITEM: true,
  mixins: [BaseMixin],
  props: initDefaultProps(FormItemProps, {
    hasFeedback: false,
    prefixCls: 'ant-form',
    colon: true,
  }),
  inject: {
    FormProps: { default: {} },
    decoratorFormProps: { default: {} },
  },
  data() {
    return { helpShow: false };
  },
  mounted() {
    warning(
      this.getControls(this.slotDefault, true).length <= 1,
      '`Form.Item` cannot generate `validateStatus` and `help` automatically, ' +
        'while there are more than one `getFieldDecorator` in it.',
    );
    warning(
      !this.fieldDecoratorId,
      '`fieldDecoratorId` is deprecated. please use `v-decorator={id, options}` instead.',
    );
  },
  methods: {
    getHelpMessage() {
      const help = getComponentFromProp(this, 'help');
      const onlyControl = this.getOnlyControl();
      if (help === undefined && onlyControl) {
        const errors = this.getField().errors;
        if (errors) {
          return intersperse(
            errors.map((e, index) => {
              return isValidElement(e.message)
                ? cloneElement(e.message, { key: index })
                : e.message;
            }),
            ' ',
          );
        } else {
          return '';
        }
      }

      return help;
    },

    getControls(childrenArray = [], recursively) {
      let controls = [];
      for (let i = 0; i < childrenArray.length; i++) {
        if (!recursively && controls.length > 0) {
          break;
        }

        const child = childrenArray[i];
        if (!child.tag && child.text.trim() === '') {
          continue;
        }

        if (getSlotOptions(child).__ANT_FORM_ITEM) {
          continue;
        }
        const children = getAllChildren(child);
        const attrs = (child.data && child.data.attrs) || {};
        if (FIELD_META_PROP in attrs) {
          // And means FIELD_DATA_PROP in child.props, too.
          controls.push(child);
        } else if (children) {
          controls = controls.concat(this.getControls(children, recursively));
        }
      }
      return controls;
    },

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

    getChildAttr(prop) {
      const child = this.getOnlyControl();
      let data = {};
      if (!child) {
        return undefined;
      }
      if (child.data) {
        data = child.data;
      } else if (child.$vnode && child.$vnode.data) {
        data = child.$vnode.data;
      }
      return data[prop] || data.attrs[prop];
    },

    getId() {
      return this.getChildAttr('id');
    },

    getMeta() {
      return this.getChildAttr(FIELD_META_PROP);
    },

    getField() {
      return this.getChildAttr(FIELD_DATA_PROP);
    },

    onHelpAnimEnd(_key, helpShow) {
      this.helpShow = helpShow;
      if (!helpShow) {
        this.$forceUpdate();
      }
    },

    renderHelp() {
      const prefixCls = this.prefixCls;
      const help = this.getHelpMessage();
      const children = help ? (
        <div class={`${prefixCls}-explain`} key="help">
          {help}
        </div>
      ) : null;
      if (children) {
        this.helpShow = !!children;
      }
      const transitionProps = getTransitionProps('show-help', {
        afterEnter: () => this.onHelpAnimEnd('help', true),
        afterLeave: () => this.onHelpAnimEnd('help', false),
      });
      return (
        <transition {...transitionProps} key="help">
          {children}
        </transition>
      );
    },

    renderExtra() {
      const { prefixCls } = this;
      const extra = getComponentFromProp(this, 'extra');
      return extra ? <div class={`${prefixCls}-extra`}>{extra}</div> : null;
    },

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

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

      let classes = `${props.prefixCls}-item-control`;
      if (validateStatus) {
        classes = classNames(`${props.prefixCls}-item-control`, {
          'has-feedback': props.hasFeedback || validateStatus === 'validating',
          'has-success': validateStatus === 'success',
          'has-warning': validateStatus === 'warning',
          'has-error': validateStatus === 'error',
          'is-validating': validateStatus === 'validating',
        });
      }
      let iconType = '';
      switch (validateStatus) {
        case 'success':
          iconType = 'check-circle';
          break;
        case 'warning':
          iconType = 'exclamation-circle';
          break;
        case 'error':
          iconType = 'close-circle';
          break;
        case 'validating':
          iconType = 'loading';
          break;
        default:
          iconType = '';
          break;
      }
      const icon =
        props.hasFeedback && iconType ? (
          <span class={`${props.prefixCls}-item-children-icon`}>
            <Icon type={iconType} theme={iconType === 'loading' ? 'outlined' : 'filled'} />
          </span>
        ) : null;
      return (
        <div class={classes}>
          <span class={`${props.prefixCls}-item-children`}>
            {c1}
            {icon}
          </span>
          {c2}
          {c3}
        </div>
      );
    },

    renderWrapper(children) {
      const { prefixCls, wrapperCol = {} } = this;
      const { class: cls, style, id, on, ...restProps } = wrapperCol;
      const className = classNames(`${prefixCls}-item-control-wrapper`, cls);
      const colProps = {
        props: restProps,
        class: className,
        key: 'wrapper',
        style,
        id,
        on,
      };
      return <Col {...colProps}>{children}</Col>;
    },

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

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

    // Resolve duplicated ids bug between different forms
    // https://github.com/ant-design/ant-design/issues/7351
    onLabelClick(e) {
      const label = getComponentFromProp(this, 'label');
      const id = this.id || 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 = this.$el.querySelector(`[id="${id}"]`);
        if (control && control.focus) {
          control.focus();
        }
      }
    },

    renderLabel() {
      const { prefixCls, labelCol = {}, colon, id } = this;
      const label = getComponentFromProp(this, 'label');
      const required = this.isRequired();
      const {
        class: labelColClass,
        style: labelColStyle,
        id: labelColId,
        on,
        ...restProps
      } = labelCol;
      const labelColClassName = classNames(`${prefixCls}-item-label`, labelColClass);
      const labelClassName = classNames({
        [`${prefixCls}-item-required`]: required,
      });

      let labelChildren = label;
      // Keep label is original where there should have no colon
      const haveColon = colon && this.FormProps.layout !== 'vertical';
      // Remove duplicated user input colon
      if (haveColon && typeof label === 'string' && label.trim() !== '') {
        labelChildren = label.replace(/[：|:]\s*$/, '');
      }
      const colProps = {
        props: restProps,
        class: labelColClassName,
        key: 'label',
        style: labelColStyle,
        id: labelColId,
        on,
      };

      return label ? (
        <Col {...colProps}>
          <label
            for={id || this.getId()}
            class={labelClassName}
            title={typeof label === 'string' ? label : ''}
            onClick={this.onLabelClick}
          >
            {labelChildren}
          </label>
        </Col>
      ) : null;
    },
    renderChildren() {
      return [
        this.renderLabel(),
        this.renderWrapper(
          this.renderValidateWrapper(this.slotDefault, this.renderHelp(), this.renderExtra()),
        ),
      ];
    },
    renderFormItem(children) {
      const props = this.$props;
      const prefixCls = props.prefixCls;
      const itemClassName = {
        [`${prefixCls}-item`]: true,
        [`${prefixCls}-item-with-help`]: this.helpShow,
        [`${prefixCls}-item-no-colon`]: !props.colon,
      };

      return <Row class={classNames(itemClassName)}>{children}</Row>;
    },
    decoratorOption(vnode) {
      if (vnode.data && vnode.data.directives) {
        const directive = find(vnode.data.directives, ['name', 'decorator']);
        warning(
          !directive || (directive && Array.isArray(directive.value)),
          `Invalid directive: type check failed for directive "decorator". Expected Array, got ${typeof directive.value}. At ${
            vnode.tag
          }.`,
        );
        return directive ? directive.value : null;
      } else {
        return null;
      }
    },
    decoratorChildren(vnodes) {
      const { FormProps } = this;
      const getFieldDecorator = FormProps.form.getFieldDecorator;
      vnodes.forEach((vnode, index) => {
        if (vnode.children) {
          vnode.children = this.decoratorChildren(cloneVNodes(vnode.children));
        } else if (vnode.componentOptions && vnode.componentOptions.children) {
          vnode.componentOptions.children = this.decoratorChildren(
            cloneVNodes(vnode.componentOptions.children),
          );
        }
        const option = this.decoratorOption(vnode);
        if (option && option[0]) {
          vnodes[index] = getFieldDecorator(option[0], option[1])(vnode);
        }
      });
      return vnodes;
    },
  },

  render() {
    const {
      $slots,
      decoratorFormProps,
      fieldDecoratorId,
      fieldDecoratorOptions = {},
      FormProps,
    } = this;
    let child = filterEmpty($slots.default || []);
    if (decoratorFormProps.form && fieldDecoratorId && child.length) {
      const getFieldDecorator = decoratorFormProps.form.getFieldDecorator;
      child[0] = getFieldDecorator(fieldDecoratorId, fieldDecoratorOptions)(child[0]);
      warning(
        !(child.length > 1),
        '`autoFormCreate` just `decorator` then first children. but you can use JSX to support multiple children',
      );
      this.slotDefault = child;
    } else if (FormProps.form) {
      child = cloneVNodes(child);
      this.slotDefault = this.decoratorChildren(child);
    } else {
      this.slotDefault = child;
    }

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