import React, { useState, useRef, useEffect, useCallback, FC } from 'react';

import classNames from 'classnames';

export interface CollapsibleProps {
  /** 隐藏时是否渲染 dom 结构，特殊情况下必须渲染，例如包含 Form，否则取不到值 */
  forceRender?: boolean;
  /** 是否显示 */
  open: boolean;
  /** 自定义 css 动画样式 */
  transition?: {
    duration?: string;
    timingFunction?: string;
  };
}

type AnimationState = 'idle' | 'measuring' | 'animating';

const Collapsible: FC<CollapsibleProps> = ({ forceRender, open, transition, children }) => {
  const [height, setHeight] = useState(0);
  const [isOpen, setIsOpen] = useState(open);
  const [animationState, setAnimationState] = useState<AnimationState>('idle');
  const collapsibleContainer = useRef<HTMLDivElement>(null);

  const isFullyOpen = animationState === 'idle' && open && isOpen;
  const isFullyClosed = animationState === 'idle' && !open && !isOpen;
  const content = forceRender || !isFullyClosed ? children : null;

  const wrapperClassName = classNames({
    collapsible: true,
    'collapsible-is-fully-closed': isFullyClosed,
    forceRender,
  });

  const collapsibleStyles = {
    ...(transition && {
      transitionDuration: `${transition.duration}`,
      transitionTimingFunction: `${transition.timingFunction}`,
    }),
    ...{
      // stylelint-disable
      maxHeight: isFullyOpen ? 'none' : `${height}px`,
      overflow: isFullyOpen ? 'visible' : 'hidden',
    },
  };

  const handleCompleteAnimation = useCallback(
    ({ target }: React.TransitionEvent<HTMLDivElement>) => {
      if (target === collapsibleContainer.current) {
        setAnimationState('idle');
        setIsOpen(open);
      }
    },
    [open],
  );

  useEffect(() => {
    if (open !== isOpen) {
      setAnimationState('measuring');
    }
  }, [open, isOpen]);

  useEffect(() => {
    if (!open || !collapsibleContainer.current) return;
    setHeight(collapsibleContainer.current.scrollHeight);
  }, []);

  useEffect(() => {
    if (!collapsibleContainer.current) return;

    // eslint-disable-next-line default-case
    switch (animationState) {
      case 'idle':
        break;
      case 'measuring':
        setHeight(collapsibleContainer.current.scrollHeight);
        setAnimationState('animating');
        break;
      case 'animating':
        setHeight(open ? collapsibleContainer.current.scrollHeight : 0);
    }
  }, [animationState, open, isOpen]);

  return (
    <div
      style={collapsibleStyles}
      ref={collapsibleContainer}
      className={wrapperClassName}
      onTransitionEnd={handleCompleteAnimation}
    >
      {content}
    </div>
  );
};

export default Collapsible;
