import clsx from 'clsx';
import type {
  ChangeEvent,
  FocusEvent,
  ForwardedRef,
  InputHTMLAttributes,
} from 'react';
import React, { forwardRef, useId } from 'react';

type Attributes = Pick<
  InputHTMLAttributes<HTMLInputElement>,
  | 'autoComplete'
  | 'disabled'
  | 'max'
  | 'maxLength'
  | 'min'
  | 'minLength'
  | 'name'
  | 'onBlur'
  | 'onFocus'
  | 'pattern'
  | 'placeholder'
  | 'required'
  | 'type'
>;

type Props = Readonly<{
  defaultValue?: string;
  endIcon?: React.ComponentType<React.ComponentProps<'svg'>>;
  errorMessage?: React.ReactNode;
  id?: string;
  isLabelHidden?: boolean;
  label: string;
  onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
  onChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void;
  startIcon?: React.ComponentType<React.ComponentProps<'svg'>>;
  value?: string;
}> &
  Readonly<Attributes>;

type State = 'error' | 'normal';

const stateClasses: Record<State, string> = {
  error:
    'border-danger-300 text-danger-900 placeholder-danger-300 focus:outline-none focus:ring-danger-500 focus:border-danger-500',
  normal:
    'placeholder:text-slate-400 focus:ring-primary-500 focus:border-primary-500 border-slate-300',
};

function TextInput(
  {
    defaultValue,
    disabled,
    endIcon: EndIcon,
    errorMessage,
    id: idParam,
    isLabelHidden = false,
    label,
    required,
    startIcon: StartIcon,
    type = 'text',
    value,
    onChange,
    ...props
  }: Props,
  ref: ForwardedRef<HTMLInputElement>,
) {
  const hasError = errorMessage != null;
  const generatedId = useId();
  const id = idParam ?? generatedId;
  const errorId = useId();
  const state: State = hasError ? 'error' : 'normal';

  return (
    <div>
      <label
        className={clsx(
          isLabelHidden
            ? 'sr-only'
            : 'block text-sm font-medium text-slate-700',
        )}
        htmlFor={id}>
        {label}
        {required && <span className="text-danger-500 not-sr-only"> *</span>}
      </label>
      <div className="relative mt-1">
        {StartIcon && (
          <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
            <StartIcon aria-hidden="true" className="h-5 w-5 text-slate-400" />
          </div>
        )}
        <input
          ref={ref}
          aria-describedby={hasError ? errorId : undefined}
          aria-invalid={hasError ? true : undefined}
          className={clsx(
            'block w-full rounded-md sm:text-sm',
            StartIcon && 'pl-10',
            EndIcon && 'pr-10',
            stateClasses[state],
            disabled && 'bg-slate-100',
          )}
          defaultValue={defaultValue}
          disabled={disabled}
          id={id}
          required={required}
          type={type}
          value={value != null ? value : undefined}
          onChange={(event) => {
            if (!onChange) {
              return;
            }

            onChange(event.target.value, event);
          }}
          {...props}
        />
        {EndIcon && (
          <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
            <EndIcon aria-hidden="true" className="h-5 w-5 text-slate-400" />
          </div>
        )}
      </div>
      {errorMessage && (
        <p className="text-danger-600 mt-2 text-sm" id={errorId}>
          {errorMessage}
        </p>
      )}
    </div>
  );
}

export default forwardRef(TextInput);