// @flow
import React, { useMemo, useState } from 'react';
import { v4 } from 'uuid';
import { pick, omit } from 'lodash';
import type { TextInputProps } from './text-input.proptypes';
import { Box } from '../../../primitives/box';
import { useDisableAutofill } from '../../../utils';
import { ErrorIcon, Lock, SearchIcon } from '../../icon';
import {
  Wrapper,
  Input,
  RelativeBox,
  ClearButton,
  ShowHideButton,
  IconBox,
} from './components';
import {
  FloatingLabel,
  ErrorMessage,
  SuccessMessage,
  Description,
} from '../internals/forms.common';
import { getDomType, getMaskOptions } from './helpers';
import { FieldLabel } from '../../typography';
import { HideAccessibly } from '../../../primitives/hide-accessibly';
import { INPUT_ROLE_ARIA_PROPS } from '../../../mixins/aria';

export const TextInput = <T>({
  id = '',
  type = 'text',
  placeholder,
  label,
  labelStyle = 'floating',
  value,
  disabled = false,
  readOnly = false,
  focusOnMount,
  fontSize = 'f2',
  error,
  success,
  description,
  customMaskOptions,
  isEmbeddable = false,
  inputMode,
  onChange = (_value?: T, _formattedValue: string) => {},
  onBlur,
  onFocus,
  onRef,
  hideValueButtonLabel,
  revealValueButtonLabel,
  'data-qa': dataQa,
  'data-cy': dataCy,
  name,
  autocomplete,
  locked,
  onClear,
  clearButtonLabel,
  currencyCode,
  decimalPlaces,
  ...props
}: TextInputProps<T>) => {
  const inputId = useMemo(() => `input-${id}-${v4()}`, [id]);
  const descriptionId = useMemo(() => `description-${id}-${v4()}`, [id]);
  const [textVisible, setTextVisible] = useState(type !== 'password');
  const disableAutofillProps = useDisableAutofill(autocomplete);
  const maskOptions = useMemo(
    () => getMaskOptions(type, currencyCode, decimalPlaces, customMaskOptions),
    [type, currencyCode, decimalPlaces, customMaskOptions],
  );

  if (labelStyle === 'hidden') {
    // eslint-disable-next-line no-console
    console.warn(
      'LabelStyle="hidden" is deprecated for a11y reasons and will be removed in a future release.',
    );
  }

  const hasErrorMessage = error != null && typeof error !== 'boolean';
  const hasText = value != null && value !== '';
  const showMessage =
    hasErrorMessage ||
    (description != null && description !== '') ||
    (success != null && success !== '');

  // Cleave's ref handling needs to be updated. See https://github.com/nosir/cleave.js/issues/497
  // For now we need to work around it with a custom callback prop instead of using
  // forwardRef.
  const handleHtmlRef = (ref) => {
    if (onRef != null) {
      onRef(ref);
    }
  };

  const handleClear = () => {
    onChange();
    if (onClear) {
      onClear();
    }
  };

  const handleChange = (event) => {
    onChange(event.target.rawValue, event.target.value);
  };

  return (
    <Box
      data-qa={dataQa}
      data-cy={dataCy}
      id={id}
      stacked="column"
      {...omit(props, INPUT_ROLE_ARIA_PROPS)}
    >
      {labelStyle === 'external' && (
        <FieldLabel htmlFor={inputId}>{label}</FieldLabel>
      )}
      {labelStyle === 'external-large' && (
        <FieldLabel htmlFor={inputId} spacing="mb2">
          {label}
        </FieldLabel>
      )}
      {labelStyle === 'hidden' && (
        <HideAccessibly>
          <label htmlFor={inputId}>{label}</label>
        </HideAccessibly>
      )}
      <Wrapper
        stacked="row"
        align="center"
        justify="space-between"
        disabled={disabled}
        readOnly={readOnly}
        isEmbeddable={isEmbeddable}
        labelStyle={labelStyle}
      >
        {type === 'search' && (
          <IconBox
            labelStyle={labelStyle}
            stacked="row"
            align="center"
            spacing="pl1"
          >
            <SearchIcon />
          </IconBox>
        )}
        <RelativeBox grow={1} stacked="column" align="stretch">
          {labelStyle === 'floating' && (
            <FloatingLabel
              htmlFor={inputId}
              fontSize={fontSize}
              isEmbeddable={isEmbeddable}
              hasLeftIcon={type === 'search'}
              labelStyle={labelStyle}
              data-has-value={value != null && value !== ''}
            >
              {label}
            </FloatingLabel>
          )}
          <Input
            id={inputId}
            type={getDomType(type, textVisible)}
            disabled={disabled}
            readOnly={readOnly}
            value={value != null ? String(value) : ''}
            onChange={handleChange}
            onFocus={onFocus}
            onBlur={onBlur}
            inputMode={inputMode}
            options={maskOptions}
            htmlRef={handleHtmlRef}
            name={name}
            spellCheck={
              type === 'email' || type === 'password' ? false : undefined
            }
            {...disableAutofillProps}
            placeholder={placeholder}
            error={error}
            fontSize={fontSize}
            isEmbeddable={isEmbeddable}
            labelStyle={labelStyle}
            {...pick(props, INPUT_ROLE_ARIA_PROPS)}
            aria-invalid={!!error}
            aria-describedby={descriptionId}
            size="1"
            data-has-value={value != null && value !== ''}
            autoFocus={focusOnMount}
          />
        </RelativeBox>
        <IconBox
          labelStyle={labelStyle}
          stacked="row"
          align="center"
          spacing="pr1"
        >
          {error != null && error !== false && (
            <Box aria-hidden stacked="row" align="center">
              <ErrorIcon />
            </Box>
          )}
          {locked && <Lock />}
          {onClear && clearButtonLabel != null && hasText && (
            <ClearButton onClick={handleClear} label={clearButtonLabel} />
          )}
          {type === 'password' && (
            <ShowHideButton
              textVisible={textVisible}
              onToggle={() => setTextVisible((old) => !old)}
              hideLabel={hideValueButtonLabel}
              revealLabel={revealValueButtonLabel}
              disabled={disabled}
            />
          )}
        </IconBox>
      </Wrapper>
      <Box id={descriptionId} spacing={showMessage ? 'mb1' : 'mb0'}>
        <Box aria-live="polite">
          {hasErrorMessage && error != null && <ErrorMessage message={error} />}
          {success != null && <SuccessMessage message={success} />}
        </Box>
        {description != null && <Description message={description} />}
      </Box>
    </Box>
  );
};

TextInput.displayName = 'TextInput';
