import { memo, useCallback } from 'react';
import classnames from 'classnames';
import css from 'styled-jsx/css';

import type { CSSProperties, ReactNode } from 'react';
import type { ClickableProps } from '../Clickable';
import type { Icon } from '../Icon/toIcon';
import type { TextProps } from '../Text';

import Box from '../Box';
import Clickable from '../Clickable';
import ChevronIcon from '../Icon/ChevronIcon';
import Loading from '../Loading';
import Text from '../Text';

const styles = css.resolve`
  .withBackgroundHoverStyle:before,
  .withButtonActiveStyle:after {
    content: '';
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    right: 0;
    z-index: 0;
    background-color: #fff;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.5s;
  }

  .withButtonActiveStyle:after {
    transition-delay: 0.1s;
    background: #000;
  }

  .withButtonActiveStyle:active:before {
    opacity: 0;
  }

  .withButtonActiveStyle:active:after,
  .withButtonActiveStyle[disabled]:after {
    opacity: 0.35;
    transition-delay: 0s;
    transition-duration: 0s;
  }

  [disabled] {
    pointer-events: none !important;
  }

  .withButtonFocusStyle:focus-visible {
    box-shadow: 0px 0px 0em 0.07em rgba(255, 255, 255, 0.27) !important;
  }

  .withChevron :global(.after) {
    transition: transform 300ms;
  }

  .withChevron:hover :global(.after),
  .withButtonFocusStyle.withChevron:focus-visible :global(.after) {
    transform: translateX(0.15rem);
    transition-duration: 0ms;
  }

  .withBeforeHoverStyle :global(.before) {
    opacity: 0.85;
  }

  .withBeforeHoverStyle:hover :global(.before),
  .withBeforeHoverStyle:focus-visible :global(.before) {
    opacity: 1;
  }

  .withBackgroundHoverStyle:hover:before {
    opacity: 0.1;
    transition-delay: 0s;
    transition-duration: 0s;
  }
`;

export interface ButtonProps extends Omit<ClickableProps, 'children'> {
  name?: string;
  isDisabled?: boolean;
  isUppercase?: boolean;
  isInverted?: boolean;
  margin?: string;
  height?: number | string;
  width?: number | string;
  text: string;
  textProps?: Omit<TextProps, 'children'>;
  isLoading?: boolean;
  renderAfter?: () => ReactNode;
  renderBefore?: () => ReactNode;
  Icon?: Icon;
  className?: string;
  withChevron?: boolean;

  /**
   * Enable icon opacity hover effect.
   *
   * @default true
   */
  withBeforeHoverStyle?: boolean;

  withBackgroundHoverStyle?: boolean;
}

const Button = memo<ButtonProps>(
  ({
    isDisabled = false,
    isInverted,
    isCentered = true,
    withChevron,
    isLoading = false,
    height = '4.8rem',
    text,
    textProps,
    renderAfter,
    renderBefore,
    Icon,
    style,
    className,
    withBeforeHoverStyle = true,
    withBackgroundHoverStyle = true,
    withActiveStyle = true,
    withFocusStyle = true,
    isUppercase = true,
    isInline,
    ...clickableProps
  }) => {
    const styleInternal: CSSProperties = {
      // We set the font-size to the height here so that we can
      // use `em` values on nested elements relative to the height,
      // so when we scale the height, everything else adjusts relatively.
      // WARNING: this is susceptible to breakage within browsers that
      // can globally override font-size (eg. facebook browser).
      fontSize: height,

      // Enforce the height here to prevent the button size being
      // changed based on any browser font-size overrides. This
      // happens in the Facebook in-app browser if the Android
      // OS font-size is changed from the default. If an 'em'
      // value is used for height here then the button will
      // be subject to these oddities!
      height: '1em',

      overflow: 'hidden',
      color: '#fff',
      backgroundColor: '#1a1a1a',

      borderRadius: '0.07em',
      borderColor: '#444',
      borderWidth: '1px',
      borderStyle: 'solid',
    };

    if (isInverted) {
      styleInternal.backgroundColor = '#eee';
      styleInternal.color = '#111';
      styleInternal.border = '';
    }

    if (isLoading) {
      isDisabled = true;
    }

    const beforeContent = useCallback(() => {
      const content = Icon ? (
        <Icon size="0.47em" />
      ) : (
        renderBefore && renderBefore()
      );

      if (content) {
        return (
          <Box
            className="before"
            centerContent
            positionAbsolute={!isInline}
            left="0.2em"
            top={0}
            bottom={0}
          >
            {content}
          </Box>
        );
      }
    }, [Icon, renderBefore])();

    const afterContent = useCallback(() => {
      const content =
        (renderAfter && renderAfter()) ||
        (withChevron && (
          <ChevronIcon size=".36em" direction="right" margin="0 0 0 -.1em" />
        ));

      if (content) {
        return (
          <Box
            positionAbsolute={!isInline}
            right="0.17em"
            top={0}
            bottom={0}
            centerContent
            className="after"
          >
            {content}
          </Box>
        );
      }
    }, [Icon, renderAfter])();

    return (
      <Clickable
        {...clickableProps}
        positionRelative
        // we're doing active/focus styling at this level so opt-out of default
        withFocusStyle={false}
        withActiveStyle={false}
        isInline={isInline}
        className={classnames(styles.className, className, 'Button', {
          withBeforeHoverStyle,
          withBackgroundHoverStyle,
          withChevron: withChevron && !renderAfter,
          // using unique class-name to not conflict with <Clickable> selectors
          withButtonActiveStyle: withActiveStyle,
          withButtonFocusStyle: withFocusStyle,
        })}
        style={{ ...styleInternal, ...style }}
        padding={isInline ? '0 .25em' : '0 .2em'}
        centerContent={isCentered}
        isDisabled={isDisabled}
      >
        {(() => {
          if (isLoading) {
            return <Loading isInverted={isInverted} size=".4em" />;
          }

          const margin = (() => {
            if (isInline) {
              if (Icon) {
                return '0 0 0 0.5em';
              }

              return '0 .2em';
            }

            if (beforeContent) {
              return '0 .6em 0 1em';
            }

            return '0 .6em';
          })();

          return (
            <>
              {beforeContent}
              <Text
                size=".3em"
                letterSpacing={isUppercase ? 0.125 : 0.02}
                margin={margin}
                weight={
                  isUppercase
                    ? 'regular'
                    : // lowercase -> bold
                      'bold'
                }
                textTransform={isUppercase ? 'uppercase' : undefined}
                lineHeight="1.2em"
                withEllipsis
                {...textProps}
              >
                {text}
              </Text>
              {afterContent}
            </>
          );
        })()}
        {styles.styles}
      </Clickable>
    );
  }
);

export default Button;
