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

// TODO implement web-client text inputs!

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

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

const LABEL_VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = {
  [MultilineTextInputSize.SMALL]: '0.125rem',
  [MultilineTextInputSize.MEDIUM]: '0.25rem',
  [MultilineTextInputSize.LARGE]: '0.5rem',
}

const VERTICAL_PADDING_SIZES: Record<MultilineTextInputSize, string | number> = {
  [MultilineTextInputSize.SMALL]: '0.6rem',
  [MultilineTextInputSize.MEDIUM]: '0.85rem',
  [MultilineTextInputSize.LARGE]: '1.25rem',
}

const INPUT_FONT_SIZES: Record<MultilineTextInputSize, string | number> = {
  [MultilineTextInputSize.SMALL]: '0.85em',
  [MultilineTextInputSize.MEDIUM]: '0.85em',
  [MultilineTextInputSize.LARGE]: '1em',
}

const SECONDARY_TEXT_SIZES: Record<MultilineTextInputSize, string | number> = {
  [MultilineTextInputSize.SMALL]: '0.65em',
  [MultilineTextInputSize.MEDIUM]: '0.75em',
  [MultilineTextInputSize.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,
  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',
  verticalAlign: 'top',
  paddingTop: 0,
  paddingBottom: 0,
  boxSizing: 'border-box',
  position: 'relative',
  border: 0,
  borderRadius: 'inherit',
  margin: 0,
  font: 'inherit',
  minHeight: '4rem',
  minWidth: '8rem',
  maxWidth: '100%',
  width: '100%',
  zIndex: 1,
  transitionProperty: 'background-color, color',
  ':focus': {
    outline: 0,
    color: 'var(--color-fg, black)',
  },
  ':disabled': {
    cursor: 'not-allowed',
  },
  '@media only screen': {
    backgroundColor: 'var(--color-bg, white)',
    color: 'var(--color-fg, black)',
  },
})

Input.displayName = 'input'

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

TextArea.displayName = 'textarea'

const HintWrapper = styled('span')({
  boxSizing: 'border-box',
  position: 'absolute',
  left: 0,
  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,
  /**
   * 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,
  /**
   * How many rows should the component display if it accepts multiline input?
   */
  rows: PropTypes.number,
  /**
   * Does the button display a border?
   */
  border: PropTypes.bool,
  /**
   * Event handler triggered when the component changes value.
   */
  onChange: PropTypes.func,
  /**
   * Event handler triggered when the component receives focus.
   */
  onFocus: PropTypes.func,
  /**
   * Event handler triggered when the component loses focus.
   */
  onBlur: PropTypes.func,
  /**
   * Should the component be displayed with an alternate appearance?
   */
  alternate: PropTypes.bool,
  /**
   * Default value of the component.
   */
  defaultValue: PropTypes.any,
  /**
   * Value of the component.
   */
  value: PropTypes.any,
  /**
   * Name of the form field associated with this component.
   */
  name: PropTypes.string,
}

type Props = {
  label?: React.ReactNode,
  hint?: React.ReactNode,
  size?: MultilineTextInputSize,
  indicator?: React.ReactNode,
  border?: boolean,
  alternate?: boolean,
}

/**
 * Component for inputting textual values.
 *
 * This component supports multiline input and adjusts its layout accordingly.
 */
const MultilineTextInput = React.forwardRef<HTMLTextAreaElement, Props>(
  (
    {
      label = '',
      hint = '',
      indicator = null,
      size = MultilineTextInputSize.MEDIUM,
      disabled = false,
      autoResize = false,
      placeholder = '',
      rows = 3,
      border = false,
      onChange,
      onFocus,
      onBlur,
      alternate = false,
      defaultValue,
      value,
      name,
    },
    ref,
  ) => (
    <ComponentBase
      style={{
        opacity: disabled ? 0.5 : undefined,
      }}
    >
      {border && <Border />}
      <CaptureArea>
        <LabelWrapper
          style={{
            paddingLeft: alternate ? '0.5rem' : undefined,
            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 && ' '}
        <TextArea
          onChange={onChange as React.ChangeEventHandler}
          onFocus={onFocus as React.FocusEventHandler}
          onBlur={onBlur as React.FocusEventHandler}
          placeholder={placeholder!}
          ref={ref as React.Ref<HTMLTextAreaElement>}
          disabled={disabled!}
          rows={rows!}
          style={{
            height: `calc(${MIN_HEIGHTS[size!]} * ${rows})`,
            fontSize: INPUT_FONT_SIZES[size!],
            minHeight: MIN_HEIGHTS[size!],
            paddingTop: alternate ? VERTICAL_PADDING_SIZES[size!] : `calc(${SECONDARY_TEXT_SIZES[size!]} * 2)`,
            paddingBottom: VERTICAL_PADDING_SIZES[size!],
            paddingRight: indicator ? MIN_HEIGHTS[size!] : (alternate ? '1rem' : undefined),
            paddingLeft: alternate ? '1rem' : undefined,
          }}
          defaultValue={defaultValue}
          value={value}
          name={name}
        />
      </CaptureArea>
      {stringify(hint).length > 0 && ' '}
      {stringify(hint).length > 0 && (
        <HintWrapper
          style={{
            top: alternate ? undefined : '0.75rem',
            bottom: alternate ? 0 : undefined,
            paddingLeft: alternate ? '1rem' : undefined,
            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>
  ),
)

MultilineTextInput.propTypes = propTypes

MultilineTextInput.displayName = 'TextInput'

export default MultilineTextInput
