import { useCallback, useEffect, useMemo, useRef } from 'react';
import { CSSTransition } from 'react-transition-group';

import { useTestIdAttribute } from '../../../../hooks/useTestIdAttribute';
import { TestIdProps } from '../../../../types';
import { assertEmptyObject } from '../../../../utils/assertEmptyObject';
import { assertUnreachable } from '../../../../utils/assertUnreachable';
import { FormMessageType } from '../../constants';

import { StyledMessage, animationDuration } from './styled';

/** Props for {@link FormMessage} */
export interface FormMessageProps extends TestIdProps {
  /** ID that will be added to message */
  id?: string;
  /** Type of message */
  type: FormMessageType;
  /** Message text */
  children?: string;
}

/**
 * Component that should be used as form field message inside {@link FormField}.
 *
 * This message usually will be shonw below form conroller.
 */
export function FormMessage(props: FormMessageProps) {
  const { id, children, type, testId, ...rest } = props;
  assertEmptyObject(rest);

  const testIdAttribute = useTestIdAttribute();

  const lastMessageRef = useRef('');

  useEffect(() => {
    if (children) {
      lastMessageRef.current = children;
    }
  }, [children]);

  const role = useMemo(() => {
    switch (type) {
      case FormMessageType.Error:
        return 'alert';
      case FormMessageType.Description:
        return undefined;
      /* istanbul ignore next  */
      default:
        assertUnreachable(type);
    }
  }, [type]);

  const elementOriginalHeight = useRef<number>(0);
  const transitionNodeRef = useRef<HTMLDivElement | null>(null);

  const setTransitionNodeRef = useCallback((element: HTMLDivElement | null) => {
    if (element) {
      elementOriginalHeight.current = element.offsetHeight;
    } else {
      elementOriginalHeight.current = 0;
    }

    transitionNodeRef.current = element;
  }, []);

  const setTransitionNodeHeight = (height: number | undefined) => {
    /* istanbul ignore next */
    if (!transitionNodeRef.current) {
      return;
    }

    if (height === undefined) {
      transitionNodeRef.current.style.maxHeight = 'none';
    } else {
      transitionNodeRef.current.style.maxHeight = `${height}px`;
    }
  };

  return (
    <CSSTransition
      in={!!children}
      mountOnEnter
      nodeRef={transitionNodeRef}
      timeout={animationDuration}
      unmountOnExit
      onEnter={() => {
        setTransitionNodeHeight(0);
      }}
      onEntered={() => {
        setTransitionNodeHeight(undefined);
      }}
      onEntering={() => {
        setTransitionNodeHeight(elementOriginalHeight.current);
      }}
      onExit={() => {
        setTransitionNodeHeight(elementOriginalHeight.current);
      }}
      onExiting={() => {
        setTransitionNodeHeight(0);
      }}
    >
      <StyledMessage
        ref={setTransitionNodeRef}
        $type={type}
        id={id}
        role={role}
        {...{ [testIdAttribute]: testId }}
      >
        {children ?? lastMessageRef.current}
      </StyledMessage>
    </CSSTransition>
  );
}
