import PropTypes from 'prop-types';
import React, { useContext } from 'react';
import { withGlobalProps } from '../../providers/globalProps';
import { classNames } from '../../helpers/classNames/classNames';
import { transferProps } from '../../helpers/transferProps';
import { getRootColorClassName } from '../_helpers/getRootColorClassName';
import { getRootPriorityClassName } from '../_helpers/getRootPriorityClassName';
import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
import { ButtonGroupContext } from '../ButtonGroup';
import { InputGroupContext } from '../InputGroup/InputGroupContext';
import getRootLabelVisibilityClassName from './helpers/getRootLabelVisibilityClassName';
import styles from './Button.module.scss';

export const Button = React.forwardRef((props, ref) => {
  const {
    afterLabel,
    beforeLabel,
    block,
    disabled,
    endCorner,
    feedbackIcon,
    id,
    label,
    labelVisibility,
    priority,
    size,
    startCorner,
    color,
    ...restProps
  } = props;
  const buttonGroupContext = useContext(ButtonGroupContext);
  const inputGroupContext = useContext(InputGroupContext);

  if (buttonGroupContext && inputGroupContext) {
    throw new Error('Button cannot be placed both in `ButtonGroup` and `InputGroup`.');
  }

  const primaryContext = buttonGroupContext ?? inputGroupContext;

  return (
    /* No worries, `type` is always assigned correctly through props. */
    /* eslint-disable react/button-has-type */
    <button
      {...transferProps(restProps)}
      className={classNames(
        styles.root,
        getRootPriorityClassName(
          resolveContextOrProp(buttonGroupContext && buttonGroupContext.priority, priority),
          styles,
        ),
        getRootColorClassName(color, styles),
        getRootSizeClassName(
          resolveContextOrProp(primaryContext && primaryContext.size, size),
          styles,
        ),
        getRootLabelVisibilityClassName(labelVisibility, styles),
        resolveContextOrProp(buttonGroupContext && buttonGroupContext.block, block) && styles.isRootBlock,
        buttonGroupContext && styles.isRootInButtonGroup,
        inputGroupContext && styles.isRootInInputGroup,
        feedbackIcon && styles.hasRootFeedback,
      )}
      disabled={resolveContextOrProp(primaryContext && primaryContext.disabled, disabled) || !!feedbackIcon}
      id={id}
      ref={ref}
    >
      {startCorner && (
        <span className={styles.startCorner}>
          {startCorner}
        </span>
      )}
      {beforeLabel && (
        <span className={styles.beforeLabel}>
          {beforeLabel}
        </span>
      )}
      <span
        className={styles.label}
        {...(id && { id: `${id}__labelText` })}
      >
        {label}
      </span>
      {afterLabel && (
        <span className={styles.afterLabel}>
          {afterLabel}
        </span>
      )}
      {endCorner && (
        <span className={styles.endCorner}>
          {endCorner}
        </span>
      )}
      {feedbackIcon && (
        <span className={styles.feedbackIcon}>
          {feedbackIcon}
        </span>
      )}
    </button>
    /* eslint-enable react/button-has-type */
  );
});

Button.defaultProps = {
  afterLabel: undefined,
  beforeLabel: undefined,
  block: false,
  color: 'primary',
  disabled: false,
  endCorner: undefined,
  feedbackIcon: undefined,
  id: undefined,
  labelVisibility: 'xs',
  priority: 'filled',
  size: 'medium',
  startCorner: undefined,
  type: 'button',
};

Button.propTypes = {
  /**
   * Element to be displayed after label, eg. an icon.
   */
  afterLabel: PropTypes.node,
  /**
   * Element to be displayed before label, eg. an icon.
   */
  beforeLabel: PropTypes.node,
  /**
   * If `true`, the button will span the full width of its parent.
   *
   * Ignored if the component is rendered within `ButtonGroup` component
   * as the value is inherited in such case.
   */
  block: PropTypes.bool,
  /**
   * Color variant to clarify importance and meaning of the alert. Implements
   * [Action, Feedback and Neutral color collections](/docs/foundation/collections#colors).
   */
  color: PropTypes.oneOf(
    ['primary', 'secondary', 'selected', 'success', 'warning', 'danger', 'help', 'info', 'note', 'light', 'dark'],
  ),
  /**
   * If `true`, the button will be disabled.
   *
   * Ignored if the component is rendered within `ButtonGroup` component
   * as the value is inherited in such case.
   */
  disabled: PropTypes.bool,
  /**
   * Element to be displayed in the top right corner.
   */
  endCorner: PropTypes.node,
  /**
   * Element to be displayed as a feedback icon on top of button label. When defined, it implies the
   * button is in feedback state.
   */
  feedbackIcon: PropTypes.node,
  /**
   * ID of the root HTML element.
   *
   * Also serves as base for ids of nested elements:
   * * `<ID>__labelText`
   */
  id: PropTypes.string,
  /**
   * Button label.
   */
  label: PropTypes.string.isRequired,
  /**
   * Defines minimum breakpoint from which the button label will be visible.
   */
  labelVisibility: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl', 'x2l', 'x3l', 'none']),
  /**
   * Visual priority to highlight or suppress the button.
   *
   * Ignored if the component is rendered within `ButtonGroup` component
   * as the value is inherited in such case.
   */
  priority: PropTypes.oneOf(['filled', 'outline', 'flat']),
  /**
   * Size of the button.
   *
   * Ignored if the component is rendered within `ButtonGroup` or `InputGroup` component as the value is inherited in
   * such case.
   */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /**
   * Element to be displayed in the top left corner.
   */
  startCorner: PropTypes.node,
  /**
   * Set the HTML `type` attribute of the `button` element.
   */
  type: PropTypes.oneOf(['button', 'submit']),
};

export const ButtonWithGlobalProps = withGlobalProps(Button, 'Button');

export default ButtonWithGlobalProps;
