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'

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, sans-serif)',
  maxWidth: '100%',
  ':focus-within': {
    '--color-accent': 'var(--color-active, Highlight)',
  },
})

ComponentBase.displayName = 'div'

const CaptureArea = styled('label')({
  display: 'block',
  borderRadius: 'inherit',
  overflow: 'hidden',
})

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 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 Input = styled('input')({
  display: 'block',
  backgroundColor: 'var(--color-bg, white)',
  color: 'var(--color-fg, black)',
  verticalAlign: 'top',
  paddingTop: 0,
  paddingBottom: 0,
  boxSizing: 'border-box',
  position: 'relative',
  border: 0,
  borderRadius: 'inherit',
  paddingLeft: '1rem',
  margin: 0,
  font: 'inherit',
  minHeight: '4rem',
  minWidth: '8rem',
  maxWidth: '100%',
  zIndex: 1,
  transitionProperty: 'background-color, color',
  ':focus': {
    outline: 0,
    color: 'var(--color-fg, black)',
  },
  ':disabled': {
    cursor: 'not-allowed',
  },
})

Input.displayName = 'input'

const TextArea = styled('textarea')({
  display: 'block',
  backgroundColor: 'var(--color-bg, white)',
  color: 'var(--color-fg, black)',
  verticalAlign: 'top',
  width: '100%',
  boxSizing: 'border-box',
  position: 'relative',
  border: 0,
  borderRadius: 'inherit',
  paddingLeft: '1rem',
  margin: 0,
  font: 'inherit',
  minHeight: '4rem',
  minWidth: '16rem',
  maxWidth: '100%',
  zIndex: 1,
  transitionProperty: 'background-color, color',
  ':focus': {
    outline: 0,
  },
})

TextArea.displayName = 'textarea'

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',
  bottom: 0,
  right: 0,
  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']),
  /**
   * Additional description, usually graphical, indicating the nature of the component's value.
   */
  indicator: PropTypes.node,
  /**
   * Should the component accept multiple lines of input?
   */
  multiline: PropTypes.bool,
  /**
   * Is the component active?
   */
  disabled: PropTypes.bool,
  /**
   * Should the component resize itself to show all its value?
   */
  autoResize: PropTypes.bool,
  /**
   * Placeholder of the component when there is no value.
   */
  placeholder: PropTypes.string,
}

type Props = PropTypes.InferProps<typeof propTypes>

const TextInput = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, Props>(
  (
    {
      label = '',
      className = '',
      hint = '',
      indicator = null,
      size = 'medium',
      multiline = false,
      disabled = false,
      autoResize = false,
      placeholder = '',
    },
    ref,
  ) => (
    <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: indicator ? MIN_HEIGHTS[size!] : '0.5rem',
            fontSize: SECONDARY_TEXT_SIZES[size!],
          }}
        >
          {stringify(label)}
        </LabelWrapper>
        {stringify(label).length > 0 && ' '}
        {multiline && (
          <TextArea
            placeholder={placeholder!}
            ref={ref as React.Ref<HTMLTextAreaElement>}
            disabled={disabled!}
            style={{
              fontSize: INPUT_FONT_SIZES[size!],
              minHeight: MIN_HEIGHTS[size!],
              paddingTop: VERTICAL_PADDING_SIZES[size!],
              paddingBottom: VERTICAL_PADDING_SIZES[size!],
              paddingRight: indicator ? MIN_HEIGHTS[size!] : '1rem',
            }}
          />
        )}
        {!multiline && (
          <Input
            placeholder={placeholder!}
            ref={ref as React.Ref<HTMLInputElement>}
            disabled={disabled!}
            style={{
              fontSize: INPUT_FONT_SIZES[size!],
              minHeight: MIN_HEIGHTS[size!],
              paddingRight: indicator ? 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: indicator ? MIN_HEIGHTS[size!] : '1rem',
            fontSize: SECONDARY_TEXT_SIZES[size!],
          }}
        >
          ({stringify(hint)})
        </HintWrapper>
      )}
      {(indicator as PropTypes.ReactComponentLike) && (
        <IndicatorWrapper
          style={{
            width: MIN_HEIGHTS[size!],
            height: MIN_HEIGHTS[size!],
          }}
        >
          {indicator}
        </IndicatorWrapper>
      )}
    </ComponentBase>
  ),
)

TextInput.propTypes = propTypes

TextInput.displayName = 'TextInput'

export default TextInput
