import { AriaRole, ChangeEventHandler, HTMLInputTypeAttribute, ReactNode, forwardRef } from 'react';

import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { assertUnreachable } from '../../utils/assertUnreachable';
import { makeTestId } from '../../utils/makeTestId';
import { SupportedInputProps } from '../types';

import { BaseTextFieldType } from './constants';
import { BaseTextFieldContextProvider } from './contexts/BaseTextFieldContext';
import {
  StyledAbsolutePositionedPrefix,
  StyledAbsolutePositionedSuffix,
  StyledInput,
  StyledInputBorderContainer,
  StyledInputPositionContainer,
  StyledInvisibleSuffix,
} from './styled';

function getInputType(type: BaseTextFieldType): HTMLInputTypeAttribute {
  switch (type) {
    case BaseTextFieldType.Number:
      return 'number';
    case BaseTextFieldType.Password:
      return 'password';
    case BaseTextFieldType.Search:
    case BaseTextFieldType.Text:
      return 'text';
    /* istanbul ignore next */
    default:
      assertUnreachable(type);
  }
}

function getInputRole(type: BaseTextFieldType): AriaRole | undefined {
  switch (type) {
    case BaseTextFieldType.Password:
      return 'textbox';
    case BaseTextFieldType.Search:
      return 'searchbox';
    case BaseTextFieldType.Number:
    case BaseTextFieldType.Text:
      return undefined;
    /* istanbul ignore next */
    default:
      assertUnreachable(type);
  }
}

export interface BaseTextFieldProps<Value> extends SupportedInputProps<Value> {
  /**
   * Element that will be appended to {@link BaseTextField} as suffix.
   * Should be only {@link FieldSuffixIconButton} or {@link FieldSuffixTextButton}
   */
  suffix?: ReactNode;
  /**
   * Element that will be appended to {@link BaseTextField} as prefix.
   * Should be only {@link FieldPrefixIcon}.
   */
  prefix?: ReactNode;
  /**
   * Type of the `input` element.
   */
  type: BaseTextFieldType;
}

/**
 * Field that allow typing some text.
 *
 * ```tsx
 * <BaseTextField type={BaseTextFieldType.Text} value="any text" onChange={console.log} />
 * ```
 *
 * ## Prefix
 *
 * {@link BaseTextField} support only one prefix option:
 * - icon prefix by {@link FieldPrefixIcon}
 *
 * ## Suffix
 *
 * {@link BaseTextField} support two suffix variant:
 * - text button suffix by {@link FieldSuffixTextButton}
 * - icon button suffix by {@link FieldSuffixIconButton}
 *
 * ### Text button suffix
 *
 * To add button as suffix to {@link BaseTextField} use {@link FieldSuffixTextButton} as
 * {@link BaseTextFieldProps.suffix} prop.
 *
 * ### Icon button suffix
 *
 * To add button as suffix to {@link BaseTextField} use {@link FieldSuffixIconButton} as
 * {@link BaseTextFieldProps.suffix} prop.
 */
export const BaseTextField = forwardRef<HTMLInputElement, BaseTextFieldProps<string>>((props, ref) => {
  const {
    type,
    ariaInvalid,
    disabled,
    onChange,
    prefix,
    suffix,
    className,
    ariaDescribedBy,
    ariaErrorMessage,
    ariaLabel,
    id,
    onBlur,
    value,
    placeholder,
    testId,
    required,
    ariaRequired,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const testIdAttribute = useTestIdAttribute();

  const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {
    if (onChange) {
      onChange(event.target.value);
    }
  };

  return (
    <BaseTextFieldContextProvider disabled={disabled ?? false}>
      <StyledInputPositionContainer className={className} {...{ [testIdAttribute]: testId }}>
        {prefix && (
          <StyledAbsolutePositionedPrefix {...{ [testIdAttribute]: makeTestId(testId, 'prefix') }}>
            {prefix}
          </StyledAbsolutePositionedPrefix>
        )}
        <StyledInputBorderContainer $disabled={disabled} $invalid={ariaInvalid}>
          <StyledInput
            ref={ref}
            $withPrefix={!!prefix}
            aria-describedby={ariaDescribedBy}
            aria-errormessage={ariaErrorMessage}
            aria-invalid={ariaInvalid}
            aria-label={ariaLabel}
            aria-required={ariaRequired}
            disabled={disabled}
            id={id}
            onBlur={onBlur}
            onChange={handleChange}
            placeholder={placeholder}
            required={required}
            role={getInputRole(type)}
            type={getInputType(type)}
            value={value}
            {...{ [testIdAttribute]: makeTestId(testId, 'input') }}
          />
          {suffix && <StyledInvisibleSuffix aria-hidden>{suffix}</StyledInvisibleSuffix>}
        </StyledInputBorderContainer>
        {suffix && (
          <StyledAbsolutePositionedSuffix {...{ [testIdAttribute]: makeTestId(testId, 'suffix') }}>
            {suffix}
          </StyledAbsolutePositionedSuffix>
        )}
      </StyledInputPositionContainer>
    </BaseTextFieldContextProvider>
  );
});
