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

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

const LABEL_VERTICAL_PADDING_SIZES: SizeMap<string | number> = {
  small: '0.125rem',
  medium: '0.25rem',
  large: '0.5rem',
}

const VERTICAL_PADDING_SIZES: SizeMap<string | number> = {
  small: '0.6rem',
  medium: '0.85rem',
  large: '1.25rem',
}

const INPUT_FONT_SIZES: SizeMap<string | number> = {
  small: '0.85em',
  medium: '0.85em',
  large: '1em',
}

const SECONDARY_TEXT_SIZES: SizeMap<string | number> = {
  small: '0.65em',
  medium: '0.75em',
  large: '0.85em',
}

const ComponentBase = styled('div')({
  position: 'relative',
  borderRadius: '0.25rem',
  fontFamily: 'var(--font-family-base)',
  maxWidth: '100%',
  ':focus-within': {
    '--color-accent': 'var(--color-active, Highlight)',
  },
})

ComponentBase.displayName = 'div'

const CaptureArea = styled('label')({
  display: 'block',
})

CaptureArea.displayName = 'label'

const LabelWrapper = styled('span')({
  color: 'var(--color-accent, blue)',
  boxSizing: 'border-box',
  position: 'absolute',
  top: 0,
  left: 0,
  paddingLeft: '0.5rem',
  fontSize: '0.85em',
  maxWidth: '100%',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'nowrap',
  fontWeight: 'bolder',
  zIndex: 2,
  pointerEvents: 'none',
  transitionProperty: 'color',
  lineHeight: 1,
  userSelect: 'none',
})

LabelWrapper.displayName = 'span'

const Input = styled('select')({
  display: 'block',
  backgroundColor: 'var(--color-bg, white)',
  color: 'var(--color-fg, black)',
  appearance: 'none',
  boxSizing: 'border-box',
  position: 'relative',
  border: 0,
  paddingLeft: '1rem',
  margin: 0,
  font: 'inherit',
  minHeight: '4rem',
  minWidth: '8rem',
  maxWidth: '100%',
  zIndex: 1,
  cursor: 'pointer',
  transitionProperty: 'background-color, color',
  ':focus': {
    outline: 0,
  },
  ':disabled': {
    cursor: 'not-allowed',
  },
  '::-moz-focus-inner': {
    outline: 0,
    border: 0,
  },
})

Input.displayName = 'select'

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',
  zIndex: 2,
  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',
  },
  [`${ComponentBase}:focus-within &::before`]: {
    boxShadow: '0 0 0 0.375rem var(--color-accent, blue)',
  },
})

const HintWrapper = styled('span')({
  boxSizing: 'border-box',
  position: 'absolute',
  bottom: 0,
  left: 0,
  paddingLeft: '1rem',
  fontSize: '0.85em',
  opacity: 0.5,
  maxWidth: '100%',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
  whiteSpace: 'nowrap',
  zIndex: 2,
  pointerEvents: 'none',
  lineHeight: 1,
  userSelect: 'none',
})

const IndicatorWrapper = styled('span')({
  color: 'var(--color-accent, blue)',
  boxSizing: 'border-box',
  position: 'absolute',
  top: 0,
  right: 0,
  height: '100%',
  display: 'grid',
  placeContent: 'center',
  padding: '0 1rem',
  zIndex: 1,
  pointerEvents: 'none',
  transitionProperty: 'color',
  lineHeight: 1,
  userSelect: 'none',
})

const propTypes = {
  /**
   * Short textual description indicating the nature of the component's value.
   */
  label: PropTypes.any,
  /**
   * Class name for the component, used for styling.
   */
  className: PropTypes.string,
  /**
   * Short textual description as guidelines for valid input values.
   */
  hint: PropTypes.any,
  /**
   * Size of the component.
   */
  size: PropTypes.oneOf<Size>(['small', 'medium', 'large']),
  /**
   * Can multiple values be selected?
   */
  multiple: PropTypes.bool,
  /**
   * Is the component active?
   */
  disabled: PropTypes.bool,
  /**
   * CSS styles.
   */
  style: PropTypes.object,
}

type Props = PropTypes.InferProps<typeof propTypes>

const Select = React.forwardRef<HTMLSelectElement, Props>(
  (
    {
      label = '',
      className = '',
      hint = '',
      size = 'medium',
      multiple = false,
      disabled = false,
      style = {},
      ...etcProps
    },
    ref,
  ) => {
    return (
      <ComponentBase
        style={{
          opacity: disabled ? 0.5 : undefined,
        }}
      >
        <Border />
        <CaptureArea className={className!}>
          <LabelWrapper
            style={{
              paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
              paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
              paddingRight: !multiple ? MIN_HEIGHTS[size!] : '0.5rem',
              fontSize: SECONDARY_TEXT_SIZES[size!],
            }}
          >
            {stringify(label)}
          </LabelWrapper>
          {stringify(label).length > 0 && ' '}
          <Input
            {...etcProps}
            ref={ref}
            disabled={disabled!}
            multiple={multiple!}
            style={{
              ...style,
              verticalAlign: 'top',
              fontSize: INPUT_FONT_SIZES[size!],
              height: multiple ? undefined : MIN_HEIGHTS[size!],
              minHeight: MIN_HEIGHTS[size!],
              resize: multiple ? 'vertical' : undefined,
              paddingTop: VERTICAL_PADDING_SIZES[size!],
              paddingBottom: VERTICAL_PADDING_SIZES[size!],
              paddingRight: !multiple ? MIN_HEIGHTS[size!] : '1rem',
            }}
          />
        </CaptureArea>
        {stringify(hint).length > 0 && ' '}
        {stringify(hint).length > 0 && (
          <HintWrapper
            style={{
              paddingTop: LABEL_VERTICAL_PADDING_SIZES[size!],
              paddingBottom: LABEL_VERTICAL_PADDING_SIZES[size!],
              paddingRight: MIN_HEIGHTS[size!],
              fontSize: SECONDARY_TEXT_SIZES[size!],
            }}
          >
            ({stringify(hint)})
          </HintWrapper>
        )}
        {!multiple && (
          <IndicatorWrapper
            style={{
              width: MIN_HEIGHTS[size!],
            }}
          >
            <Icon name="chevron-down" label="" />
          </IndicatorWrapper>
        )}
      </ComponentBase>
    )
  },
)

Select.propTypes = propTypes

Select.displayName = 'Select'

export default Select
