import { string, bool, func, oneOf, node, number } from 'prop-types';
import * as React from 'react';
import isString from 'lodash/isString';
import css, { get } from '@styled-system/css';
import styled from '@emotion/styled';
import { createShouldForwardProp } from '@styled-system/should-forward-prop';
import Icon from '../Icon/index';
import { variant } from 'styled-system';
import { Shelf } from '../Layout';
import { useTheme } from 'emotion-theming';
import { focusVisibleRing } from '../../utils/styled';
import Loader from '../Loader';

const StyledButton = styled('span', {
  shouldForwardProp: createShouldForwardProp(['size']),
})(
  css({
    appearance: 'none',
    alignItems: 'center',
    display: 'flex',
    justifyContent: 'space-evenly',
    borderRadius: 'button',
    fontSize: 'standard',
    textAlign: 'center',
    cursor: 'pointer',
    transition: 'color 150ms, border-color 150ms, background-color 150ms, opacity 150ms',
  }),
  focusVisibleRing(),
  variant({
    prop: 'size',
    // scale: 'components.Button.sizes',
    variants: {
      regular: {
        minHeight: '40px',
        padding: '8px 16px',
      },
      narrow: {
        minWidth: 'auto',
        minHeight: '32px',
        padding: '6px',
        lineHeight: '14px',
      },
      micro: {
        minHeight: '28px',
        padding: '4px',
        minWidth: '82px',
        fontSize: '12px',
      },
    },
  }),
  ({ colors, appearance, size, width, active, theme, isIconOnly }) => {
    const {
      color,
      hover: hoverColor = color,
      active: activeColor = hoverColor,
      background = color,
      backgroundHover = hoverColor,
      backgroundActive = backgroundHover,
      minimal = color,
      minimalHover = hoverColor,
      minimalActive = minimalHover,
      foregroundOnColor = '#fff',
    } = colors;
    switch (appearance) {
      case 'primary':
        return css({
          minWidth: isIconOnly ? 'auto' : undefined,
          padding: size === 'narrow' && !isIconOnly ? '6px 10px' : undefined,
          fontWeight: 'bold',
          width,
          color: foregroundOnColor,
          backgroundColor: active ? backgroundActive : background,
          '&:hover': {
            color: foregroundOnColor,
            backgroundColor: active ? backgroundActive : backgroundHover,
          },
        })(theme);
      case 'secondary':
        return css({
          minWidth: isIconOnly ? (size === 'narrow' ? '32px' : 'auto') : undefined,
          padding: size === 'narrow' && !isIconOnly ? '6px 8px' : undefined,
          borderStyle: 'solid',
          borderWidth: 'button',
          width,
          borderColor: active ? 'borderActive' : 'border',
          backgroundColor: 'background',
          fontWeight: 'bold',
          color: active ? activeColor : color,
          '&:hover': {
            color: active ? activeColor : hoverColor,
            borderColor: 'borderActive',
          },
        })(theme);
      default:
        return {
          border: 'none',
          backgroundColor: 'transparent',
          minWidth: 'auto',
          width,
          color: active ? minimalActive : minimal,
          padding: size !== 'regular' ? 0 : undefined,
          '&:hover': {
            color: active ? minimalActive : minimalHover,
          },
        };
    }
  },
  (props) =>
    props.disabled && {
      opacity: 0.5,
      pointerEvents: 'none',
    },
);

const LoadingStateContent = styled('span')({
  display: 'grid',
  gridTemplateColumns: '100%',
  gridTemplateRows: 'auto',
  gridTemplateAreas: "'unit'",
  position: 'relative',
  '> span': {
    gridArea: 'unit',
    alignSelf: 'center',
    justifySelf: 'center',
  },
});

const iconSize = (size) => (size === 'micro' ? 16 : 20);

const getIcon = (icon, size, appearance) =>
  isString(icon) ? <Icon name={icon} size={iconSize(size, appearance)} /> : icon;

const Button = ({
  as,
  width,
  appearance = 'primary',
  size = 'regular',
  disabled = false,
  intent: intentProp,
  icon,
  iconRight,
  onClick,
  children,
  type,
  loading,
  success,
  successMessage,
  successIcon,
  loadingMessage,
  loadingIcon,
  resetTimeout = 3000,
  onReset,
  ...rest
}) => {
  const theme = useTheme();
  const config = get(theme, 'components.Button', {});
  const { defaultIntentForAppearance = {} } = config;
  const intent = intentProp || defaultIntentForAppearance[appearance] || 'neutral';
  const colors = get(config, `intents.${intent}`, { color: 'black' });

  const isIconOnly = ((icon && !iconRight) || (iconRight && !icon)) && !children;

  React.useEffect(() => {
    if (!success || !onReset) return undefined;

    const id = setTimeout(onReset, resetTimeout);

    return () => {
      clearTimeout(id);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [success]);

  const canLoad = loading !== undefined;
  const hasIcon = icon || iconRight;
  const spinnerEl = <Loader size={iconSize(size)} color="currentColor" />;
  const loadingStateIcon = success
    ? getIcon(successIcon, size, appearance)
    : loading && (getIcon(loadingIcon, size, appearance) || spinnerEl);
  const iconEl = icon && (loadingStateIcon || getIcon(icon, size, appearance));
  const iconElRight = iconRight && (loadingStateIcon || getIcon(iconRight, size, appearance));

  const Comp = as || (onClick || type ? 'button' : 'span');

  const content =
    canLoad && !isIconOnly ? (
      <LoadingStateContent>
        <span
          style={{
            opacity:
              (!loading && !success) ||
              (loading && !loadingMessage && hasIcon) ||
              (success && !successMessage)
                ? 1
                : 0,
          }}
        >
          {children}
        </span>
        {loadingMessage && <span style={{ opacity: loading ? 1 : 0 }}>{loadingMessage}</span>}
        {!hasIcon && !loadingMessage && (
          <span style={{ visibility: loading ? 'visible' : 'hidden' }}>{spinnerEl}</span>
        )}
        {successMessage && <span style={{ opacity: success ? 1 : 0 }}>{successMessage}</span>}
      </LoadingStateContent>
    ) : (
      children && <span>{children}</span>
    );

  return (
    <StyledButton
      as={Comp}
      width={width}
      type={type || (Comp === 'button' ? 'button' : undefined)}
      onClick={onClick}
      appearance={appearance}
      size={size}
      colors={colors}
      isIconOnly={isIconOnly}
      disabled={disabled || loading || success}
      {...rest}
    >
      <Shelf gap={8} flexWrap="nowrap">
        {iconEl}
        {content}
        {iconElRight}
      </Shelf>
    </StyledButton>
  );
};

const AnchorButton = (props) => (
  <Button as="a" rel="noopener noreferrer" target="_blank" {...props} />
);

Button.propTypes = {
  size: oneOf(['micro', 'narrow', 'regular']),
  intent: string,
  narrow: bool,
  className: string,
  icon: node,
  iconRight: node,
  children: node,
  onClick: func,
  appearance: oneOf(['secondary', 'minimal', 'primary']),
  disabled: bool,
  type: oneOf(['button', 'submit']),
  active: bool,
  loading: bool,
  success: bool,
  successMessage: node,
  successIcon: node,
  loadingMessage: node,
  resetTimeout: number,
  onReset: func,
};

export default Button;
export { AnchorButton };
