import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import classNamesBind from 'classnames/bind';
import ThemeContext from 'terra-theme-context';
import IconError from 'terra-icon/lib/icon/IconError';
import styles from './Field.module.scss';

const cx = classNamesBind.bind(styles);

const propTypes = {
  /**
   * The form control elements the Field contains.
   */
  children: PropTypes.node,
  /**
   * Error message for when the input is invalid. This will only be displayed if isInvalid is true.
   */
  error: PropTypes.node,
  /**
   * Error Icon to display when the input is invalid.
   */
  errorIcon: PropTypes.element,
  /**
   * Help element to display with the input.
   */
  help: PropTypes.node,
  /**
   * @private
   * Whether or not to hide the required indicator on the label.
   */
  hideRequired: PropTypes.bool,
  /**
   * The htmlFor attribute on the field label.
   */
  htmlFor: PropTypes.string,
  /**
   * Whether the field displays as Invalid. Use when value does not meet validation pattern.
   */
  isInvalid: PropTypes.bool,
  /**
   * Whether or not the field is an inline field.
   */
  isInline: PropTypes.bool,
  /**
   * @private
   * Whether or not the label is visible. Use this props to hide a label while still creating it on the DOM for accessibility.
   */
  isLabelHidden: PropTypes.bool,
  /**
   * The label of the form control children.
   */
  label: PropTypes.string.isRequired,
  /**
   * Attributes to attach to the label.
   */
  // eslint-disable-next-line react/forbid-prop-types
  labelAttrs: PropTypes.object,
  /**
   * Set the max-width of a field using `length` or `%`.  Best practice recommendation to never exceed
   * a rendered value of 1020px. _(Note: Providing custom inline styles will take precedence.)_
   */
  maxWidth: PropTypes.string,
  /**
   * Whether or not the field is required.
   */
  required: PropTypes.bool,
  /**
   * Whether or not to append the 'optional' label to a non-required field label.
   */
  showOptional: PropTypes.bool,
  /**
   *  Provides first class prop for custom inline styles
   */
  // eslint-disable-next-line react/forbid-prop-types
  style: PropTypes.object,
};

const defaultProps = {
  children: null,
  error: null,
  errorIcon: <IconError />,
  help: null,
  hideRequired: false,
  htmlFor: undefined,
  isInvalid: false,
  isInline: false,
  isLabelHidden: false,
  labelAttrs: {},
  maxWidth: undefined,
  required: false,
  showOptional: false,
};

const hasWhiteSpace = s => /\s/g.test(s);
// Detect IE 10 or IE 11
// TODO - Delete detection logic when we drop support for IE
const isIE = () => (window.navigator.userAgent.indexOf('Trident/6.0') > -1 || window.navigator.userAgent.indexOf('Trident/7.0') > -1);

const Field = (props) => {
  const {
    children,
    error,
    errorIcon,
    help,
    hideRequired,
    htmlFor,
    isInvalid,
    isInline,
    isLabelHidden,
    label,
    labelAttrs,
    maxWidth,
    required,
    showOptional,
    style,
    ...customProps
  } = props;

  const theme = React.useContext(ThemeContext);

  const customStyles = maxWidth ? ({ maxWidth, ...style }) : style;

  const fieldClasses = classNames(
    cx([
      'field',
      { 'field-inline': isInline },
      theme.className,
    ]),
    customProps.className,
  );

  const labelClassNames = cx([
    'label',
    labelAttrs.className,
  ]);

  // Checks to run when not in production
  // TODO - Remove this once we make this a required prop.
  // More info: https://github.com/cerner/terra-core/issues/2290
  if (process.env.NODE_ENV !== 'production') {
    if (!htmlFor) {
      // eslint-disable-next-line
      console.warn('The htmlFor prop will be required in the next major version bump of terra-form-field. It is needed for creating an accessible mapping from the form field to its related error and help text.');
    }

    if (htmlFor && hasWhiteSpace(htmlFor)) {
      // eslint-disable-next-line
      console.warn('The htmlFor prop should be a string without white spaces as it will be used as an HTML attribute value. Use - or _ in place of white space characters.');
    }
  }

  /**
   * IE + JAWS has trouble reading aria-describedby content with our form components.
   * Using feature detect for Microsoft browsers and injecting the help and error messages
   * into the label as visually hidden text so JAWS can announce them correctly in IE.
   */
  const IEDescriptionText = (
    isIE()
      ? (
        <div className={cx('visually-hidden-text')}>
          {isInvalid && error ? error : null}
          {help}
        </div>
      ) : null
  );

  const content = React.Children.map(children, (child) => {
    if ((required || isInvalid) && child && (child.type.isInput || child.type.isSelect || child.type.isTextarea)) {
      return React.cloneElement(child, {
        ...required && { required: true },
        ...isInvalid && { isInvalid: true, 'aria-invalid': true },
      });
    }
    return child;
  });

  const labelGroup = (
    <div className={cx(['label-group', { 'label-group-hidden': isLabelHidden }])}>
      {isInvalid && <div className={cx('error-icon')}>{errorIcon}</div>}
      <label htmlFor={htmlFor} {...labelAttrs} className={labelClassNames}>
        {required && (isInvalid || !hideRequired) && <div className={cx('required')} aria-hidden="true">*</div>}
        {label}
        {required && !isInvalid && hideRequired && <div className={cx('required-hidden')} aria-hidden="true">*</div>}
        {showOptional && !required
            && (
              <FormattedMessage id="Terra.form.field.optional">
                {optionalText => (
                  <span className={cx('optional')}>{optionalText}</span>
                )}
              </FormattedMessage>
            )}
        {IEDescriptionText}
      </label>
      {!isInvalid && <div className={cx('error-icon-hidden')}>{errorIcon}</div>}
    </div>
  );

  /* eslint-disable react/forbid-dom-props */
  return (
    <div style={customStyles} {...customProps} className={fieldClasses}>
      {labelGroup}
      {content}
      {isInvalid && error && <div id={htmlFor ? `${htmlFor}-error` : undefined} className={cx('error-text')}>{error}</div>}
      {help && <div id={htmlFor ? `${htmlFor}-help` : undefined} className={cx('help-text')}>{help}</div>}
    </div>
  );
  /* eslint-enable react/forbid-dom-props */
};

Field.propTypes = propTypes;
Field.defaultProps = defaultProps;

export default Field;
