import * as React from 'react'
import styled, { CSSObject } from 'styled-components'

export enum ButtonSize {
  SMALL = 'small',
  MEDIUM = 'medium',
  LARGE = 'large',
}

export enum ButtonVariant {
  OUTLINE = 'outline',
  PRIMARY = 'primary',
}

export enum ButtonElement {
  A = 'a',
  BUTTON = 'button',
}

export enum ButtonType {
  SUBMIT = 'submit',
  RESET = 'reset',
  BUTTON = 'button',
}

const MIN_HEIGHTS: Record<ButtonSize, string | number> = {
  [ButtonSize.SMALL]: '2.5rem',
  [ButtonSize.MEDIUM]: '3rem',
  [ButtonSize.LARGE]: '4rem',
}

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

const buttonStyles: CSSObject = {
  display: 'inline-grid',
  appearance: 'none',
  padding: '0 1rem',
  font: 'inherit',
  fontFamily: 'var(--font-family-base, sans-serif)',
  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<ButtonVariant, React.CSSProperties> = {
  outline: defaultVariantStyleSet,
  primary: {
    backgroundColor: 'var(--color-accent, blue)',
    color: 'var(--color-bg, white)',
  },
}

export type Props = Omit<React.HTMLProps<HTMLButtonElement>, 'as' | 'size'> & Omit<React.HTMLProps<HTMLAnchorElement>, 'as' | 'size'> & {
  /**
   * Size of the component.
   */
  size?: ButtonSize,
  /**
   * Variant of the component.
   */
  variant?: ButtonVariant,
  /**
   * Can the component be activated?
   */
  disabled?: boolean,
  /**
   * The corresponding HTML element of the component.
   */
  element?: ButtonElement,
  /**
   * Should the component display a border?
   */
  border?: boolean,
  /**
   * Should the component occupy the whole width of its parent?
   */
  block?: boolean,
}

type RefElement = HTMLAnchorElement | HTMLButtonElement | HTMLSpanElement

/**
 * Component for performing an action upon activation (e.g. when clicked).
 *
 * Depending on the declared props, this component can function as a hyperlink or as a button.
 */
export const Button = React.forwardRef<RefElement, Props>(
  (
    {
      size = ButtonSize.MEDIUM,
      variant = ButtonVariant.OUTLINE,
      disabled = false,
      element = ButtonElement.BUTTON,
      border = false,
      children,
      type = ButtonType.BUTTON,
      block = false,
      className: _className,
      style: _style,
      ...etcProps
    },
    ref,
  ) => {
    const { [variant]: theVariantStyleSet = defaultVariantStyleSet } = variantStyleSets
    const commonButtonStyles: React.CSSProperties = {
      ...theVariantStyleSet,
      minHeight: MIN_HEIGHTS[size!],
      width: block ? '100%' : undefined,
    }
    const buttonContent = (
      <>
        {border && <Border />}
        {children}
      </>
    )

    switch (element) {
      case ButtonElement.BUTTON:
        return (
          <Base
            {...etcProps}
            ref={ref as React.Ref<HTMLButtonElement>}
            disabled={disabled!}
            style={commonButtonStyles}
            type={type as (ButtonType | undefined)}
          >
            {buttonContent}
          </Base>
        )
      case ButtonElement.A:
        if (disabled) {
          return (
            <DisabledLinkBase
              ref={ref as React.Ref<HTMLSpanElement>}
              style={commonButtonStyles}
              role="link"
              aria-disabled="true"
            >
              {buttonContent}
            </DisabledLinkBase>
          )
        }
        return (
          <LinkBase
            {...etcProps}
            ref={ref as React.Ref<HTMLAnchorElement>}
            style={commonButtonStyles}
          >
            {buttonContent}
          </LinkBase>
        )
      default:
        break
    }

    return null
  },
)

Button.displayName = 'Button'
