import * as React from 'react'
import * as PropTypes from 'prop-types'
import styled, { CSSObject } from 'styled-components'
import { Size, SizeMap } from '../../services/utilities'
import stringify from '../../services/stringify'

export type Variant = 'outline' | 'primary'

export type ButtonElement = 'a' | 'button'

type ButtonType = 'submit' | 'reset' | 'button'

const MIN_HEIGHTS: SizeMap<string | number> = {
  small: '2.5rem',
  medium: '3rem',
  large: '4rem',
}

const disabledButtonStyles: CSSObject = {
  opacity: 0.5,
  cursor: 'not-allowed',
}

const buttonStyles: CSSObject = {
  display: 'grid',
  appearance: 'none',
  padding: '0 1rem',
  font: 'inherit',
  fontFamily: 'var(--font-family-base)',
  textTransform: 'uppercase',
  fontWeight: 'bolder',
  borderRadius: '0.25rem',
  placeContent: 'center',
  position: 'relative',
  cursor: 'pointer',
  border: 0,
  userSelect: 'none',
  textDecoration: 'none',
  transitionProperty: 'background-color, color',
  whiteSpace: 'nowrap',
  lineHeight: 1,
  ':focus': {
    '--color-accent': 'var(--color-active, Highlight)',
    outline: 0,
  },
  ':disabled': disabledButtonStyles,
  '::-moz-focus-inner': {
    outline: 0,
    border: 0,
  },
}

const disabledLinkButtonStyles: CSSObject = {
  ...buttonStyles,
  ...disabledButtonStyles,
}

const Base = styled('button')(buttonStyles)

Base.displayName = 'button'

const LinkBase = styled('a')(buttonStyles)

LinkBase.displayName = 'a'

const DisabledLinkBase = styled('span')(disabledLinkButtonStyles)

DisabledLinkBase.displayName = 'span'

const Border = styled('span')({
  borderColor: 'var(--color-accent, blue)',
  boxSizing: 'border-box',
  display: 'inline-block',
  borderWidth: '0.125rem',
  borderStyle: 'solid',
  position: 'absolute',
  top: 0,
  left: 0,
  width: '100%',
  height: '100%',
  borderRadius: 'inherit',
  pointerEvents: 'none',
  transitionProperty: 'border-color',
  '::before': {
    position: 'absolute',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%',
    content: "''",
    borderRadius: '0.125rem',
    opacity: 0.5,
    pointerEvents: 'none',
  },
  [`${Base}:focus &::before`]: {
    boxShadow: '0 0 0 0.375rem var(--color-accent, blue)',
  },
})

Border.displayName = 'span'

const defaultVariantStyleSet: React.CSSProperties = {
  backgroundColor: 'transparent',
  color: 'var(--color-accent, blue)',
}

const variantStyleSets: Record<Variant, React.CSSProperties> = {
  outline: defaultVariantStyleSet,
  primary: {
    backgroundColor: 'var(--color-accent, blue)',
    color: 'var(--color-bg, white)',
  },
}

const propTypes = {
  /**
   * Size of the component.
   */
  size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
  /**
   * Variant of the component.
   */
  variant: PropTypes.oneOf<Variant>(['outline', 'primary']),
  /**
   * Text to identify the action associated upon activation of the component.
   */
  children: PropTypes.any,
  /**
   * Can the component be activated?
   */
  disabled: PropTypes.bool,
  /**
   * The corresponding HTML element of the component.
   */
  element: PropTypes.oneOf<ButtonElement>(['a', 'button']),
  /**
   * The URL of the page to navigate to, if element is set to "a".
   */
  href: PropTypes.string,
  /**
   * The target on where to display the page navigated to, if element is set to "a".
   */
  target: PropTypes.string,
  /**
   * The relationship of the current page to the referred page in "href", if element is set to "a".
   */
  rel: PropTypes.string,
  /**
   * The type of the button, if element is set to "button".
   */
  type: PropTypes.oneOf<ButtonType>(['submit', 'reset', 'button']),
}

type Props = PropTypes.InferProps<typeof propTypes>

const Button = React.forwardRef<HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement, Props>(
  (
    {
      size = 'medium',
      variant = 'outline',
      disabled = false,
      children,
      element = 'button',
      href,
      target,
      rel,
      type = 'button',
    },
    ref,
  ) => {
    const { [variant as Variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
    const commonButtonStyles: React.CSSProperties = {
      ...theVariantStyleSet,
      minHeight: MIN_HEIGHTS[size!],
    }
    const buttonContent = (
      <React.Fragment>
        <Border />
        {stringify(children)}
      </React.Fragment>
    )

    switch (element) {
      case 'button':
        return (
          <Base type={type!} ref={ref as React.Ref<HTMLButtonElement>} disabled={disabled!} style={commonButtonStyles}>
            {buttonContent}
          </Base>
        )
      case 'a':
        if (disabled) {
          return (
            <DisabledLinkBase ref={ref as React.Ref<HTMLSpanElement>} style={commonButtonStyles}>
              {buttonContent}
            </DisabledLinkBase>
          )
        }
        return (
          <LinkBase
            href={href!}
            target={target!}
            rel={rel!}
            ref={ref as React.Ref<HTMLAnchorElement>}
            style={commonButtonStyles}
          >
            {buttonContent}
          </LinkBase>
        )
      default:
        break
    }

    return null
  },
)

Button.propTypes = propTypes

Button.displayName = 'Button'

export default Button
