import { clsx } from 'clsx';
import React, { useCallback, useState } from 'react';

import { Field, FieldProps } from '../field';
import { FieldHint } from '../field-hint';
import { Input } from '../input';
import { InputLabel, useInputLabel } from '../input-label';
import { useCombinedRef, useIsFocused } from '../utils/hooks';
import * as css from './input-field.css';

/*-- Types --*/

type InputProps = React.ComponentProps<typeof Input>;

export interface InputFieldProps
  extends Omit<
    InputProps,
    'startAddonRef' | 'endAddonRef' | 'placeholderVisible' | 'as' | 'full'
  > {
  label?: React.ReactNode;
  inputRef?: React.ForwardedRef<HTMLInputElement>;
  error?: boolean | React.ReactNode;
  hint?: React.ReactNode;
  /**
   * If `true`, it adds some space below the input so a layout doesn't jump
   * when a helper text (`error`, `hint`) appears.
   */
  hasHelperTextSpace?: boolean;
  width?: FieldProps['width'];
}

/*-- Main --*/

export const InputField = React.forwardRef<HTMLDivElement, InputFieldProps>(
  function InputField(
    {
      id,
      value: userValue,
      defaultValue = '',
      onFocus: userOnFocus,
      onBlur: userOnBlur,
      onChange: userOnChange,
      inputRef: userInputRef,
      label,
      error,
      hint,
      placeholder,
      disabled,
      hasHelperTextSpace,
      className,
      width,
      ...rest
    },
    ref,
  ) {
    const isControllable = userValue !== undefined;

    const [internalValue, setInternalValue] = useState(defaultValue);

    const value = isControllable ? userValue : internalValue;

    const { isFocused, onFocus, onBlur } = useIsFocused({
      onFocus: userOnFocus,
      onBlur: userOnBlur,
    });

    const { labelRef, startAddonRef, targetRef } = useInputLabel();

    const combinedInputRef = useCombinedRef(userInputRef, targetRef);

    const onChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
      event => {
        if (!isControllable) {
          setInternalValue(event.target.value);
        }

        userOnChange && userOnChange(event);
      },
      [isControllable, userOnChange],
    );

    const shouldLabelShrink = !!value || isFocused;
    const isInvalid = !!error;
    const hintText = error && typeof error !== 'boolean' ? error : hint;

    return (
      <Field ref={ref} invalid={isInvalid} width={width}>
        {label && (
          <InputLabel
            ref={labelRef}
            className={clsx({ [css.labelDisabled]: disabled })}
            shrinked={shouldLabelShrink}
            htmlFor={id}
          >
            {label}
          </InputLabel>
        )}

        <Input
          aria-invalid={isInvalid}
          {...rest}
          id={id}
          ref={combinedInputRef}
          className={clsx(
            className,
            css.input({ placeholderVisible: label ? shouldLabelShrink : true }),
          )}
          value={value}
          onChange={onChange}
          onFocus={onFocus}
          onBlur={onBlur}
          disabled={disabled}
          startAddonRef={startAddonRef}
          placeholder={placeholder}
          full
        />

        {hasHelperTextSpace ? (
          <FieldHint preserveSpace={hasHelperTextSpace}>{hintText}</FieldHint>
        ) : (
          hintText && <FieldHint>{hintText}</FieldHint>
        )}
      </Field>
    );
  },
);
