import React, { useEffect, useMemo, useRef } from 'react';

import { Field, FieldProps } from '../field';
import { FieldHint } from '../field-hint';
import { FieldLabel } from '../field-label';
import { Textarea } from '../textarea';
import { buildClassNameObject } from '../utils/misc';
import { useCombinedRef } from '../utils/hooks';
import { TextareaFieldCounter } from './textarea-field-counter';
import * as css from './textarea-field.css';

/*-- Types --*/

type TextareaProps = React.ComponentProps<typeof Textarea>;

export interface TextareaFieldProps
  extends Omit<TextareaProps, 'as' | 'full' | 'className'> {
  label?: React.ReactNode;
  textareaRef?: React.ForwardedRef<HTMLTextAreaElement>;
  error?: boolean | React.ReactNode;
  hint?: React.ReactNode;
  /**
   * If `true`, it adds some space below the textarea so a layout doesn't jump
   * when a helper text (`error`, `hint`) appears.
   */
  hasHelperTextSpace?: boolean;
  /**
   * If `true`, it shows a counter with the current length of the textarea.
   */
  hasCounter?: boolean;
  width?: FieldProps['width'];
  className?:
    | string
    | {
        field?: string;
        label?: string;
        textarea?: string;
        hint?: string;
      };
}

/*-- Main --*/

export const TextareaField = React.forwardRef<
  HTMLDivElement,
  TextareaFieldProps
>(function TextareaField(
  {
    id,
    value,
    onChange: userOnChange,
    textareaRef,
    label,
    error,
    hint,
    hasHelperTextSpace,
    hasCounter,
    className,
    width,
    maxLength,
    ...rest
  },
  ref,
) {
  const cn = useMemo(
    () => buildClassNameObject(className, 'textarea'),
    [className],
  );

  const isInvalid = !!error;
  const hintText = error && typeof error !== 'boolean' ? error : hint;

  const refTarget = useRef<HTMLTextAreaElement>(null);

  const combinedTextareaRef = useCombinedRef(textareaRef, refTarget);

  const isControlled = value !== undefined;

  const [currentLength, setCurrentLength] = React.useState(0);

  const onChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
    if (!isControlled) {
      setCurrentLength(event.target.value.length);
    }
    userOnChange?.(event);
  };

  useEffect(() => {
    if (!isControlled) {
      const valueLength = refTarget.current?.value.length || 0;
      setCurrentLength(valueLength);
    }
  }, [isControlled]);

  const valueLength = isControlled ? String(value).length : currentLength;

  const shouldShowFieldHint = hasHelperTextSpace || hasCounter || hintText;
  const shouldShowFieldHintContent =
    hintText || (hasCounter && typeof maxLength === 'number');

  return (
    <Field ref={ref} className={cn.field} invalid={isInvalid} width={width}>
      {label && (
        <FieldLabel className={cn.label} htmlFor={id}>
          {label}
        </FieldLabel>
      )}

      <Textarea
        aria-invalid={isInvalid}
        {...rest}
        value={value}
        maxLength={maxLength}
        onChange={onChange}
        id={id}
        ref={combinedTextareaRef}
        className={cn.textarea}
        full
      />

      {shouldShowFieldHint && (
        <FieldHint preserveSpace={!hintText && hasHelperTextSpace} as="div">
          {shouldShowFieldHintContent && (
            <div className={css.footer}>
              {hintText && <span>{hintText}</span>}

              {hasCounter && typeof maxLength === 'number' && (
                <TextareaFieldCounter
                  valueLength={valueLength}
                  maxLength={maxLength}
                  className={css.counter}
                />
              )}
            </div>
          )}
        </FieldHint>
      )}
    </Field>
  );
});
