import { Transition } from 'react-transition-group';
import { TimeoutProps } from 'react-transition-group/Transition';
import React, {
  CSSProperties,
  HTMLAttributes,
  ReactElement,
  RefCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { st, classes } from './Collapse.st.css.js';
import { TransitionState } from '@wix/bex-core';
import { observer } from 'mobx-react-lite';
import { action, runInAction } from 'mobx';

export interface CollapseProps<T> extends Omit<TimeoutProps<undefined>, 'in'> {
  children: ReactElement<{ className?: string; style?: CSSProperties }>;
  state: TransitionState<T>;
  dataHook?: string;
  style?: HTMLAttributes<HTMLDivElement>['style'];
  rootRef?: RefCallback<HTMLDivElement | null>;
}

function _Collapse<T>(props: CollapseProps<T>) {
  const {
    children,
    timeout,
    state,
    onExit,
    onExiting,
    onExited,
    onEnter,
    onEntering,
    onEntered,
    dataHook,
    rootRef,
    style: styleProp,
    ...rest
  } = props;

  const ref = useRef<HTMLDivElement | null>(null);
  const [initialStyle, setInitialStyle] = useState(false);
  const enterTimeoutRef = useRef<number>();
  const exitTimeoutRef = useRef<number>();
  const { status, show } = state;

  useLayoutEffect(() => {
    setInitialStyle(true);
    runInAction(() => {
      if (state.show) {
        state.status = 'entered';
      }
    });
  }, []);

  useEffect(() => {
    return () => {
      window.clearTimeout(enterTimeoutRef.current);
      window.clearTimeout(exitTimeoutRef.current);
    };
  }, []);

  const statusStyle = useMemo(
    () =>
      initialStyle
        ? {
            ...(status !== 'afterEntered' && { overflow: 'hidden' }),
            ...(status !== 'afterEntered' && {
              height:
                status === 'exited' || status === 'exiting'
                  ? 0
                  : ref.current?.scrollHeight,
            }),
          }
        : {},
    [status, initialStyle],
  );

  const style = useMemo(
    () => ({
      transitionDuration: `${timeout}ms`,
      ...styleProp,
      ...statusStyle,
    }),
    [timeout, styleProp, statusStyle],
  );

  return (
    <Transition
      in={show}
      onEnter={(node, isAppearing) => {
        state.setStatus('enter');
        onEnter?.(node, isAppearing);
      }}
      onEntering={(node, isAppearing) => {
        state.setStatus('entering');
        onEntering?.(node, isAppearing);
      }}
      onEntered={(node, isAppearing) => {
        state.setStatus('entered');
        enterTimeoutRef.current = window.setTimeout(
          action(() => {
            if (state.status === 'entered') {
              state.setStatus('afterEntered');
            }
          }),
          0,
        );
        onEntered?.(node, isAppearing);
      }}
      onExit={(node) => {
        state.setStatus('exit');
        onExit?.(node);
      }}
      onExiting={(node) => {
        state.setStatus('beforeExit');
        exitTimeoutRef.current = window.setTimeout(
          action(() => {
            if (state.status === 'beforeExit') {
              state.setStatus('exiting');
            }
          }),
          0,
        );
        onExiting?.(node);
      }}
      onExited={(node) => {
        state.setStatus('exited');
        onExited?.(node);
      }}
      timeout={timeout}
      {...rest}
    >
      <div
        data-hook={dataHook}
        ref={(el) => {
          ref.current = el;
          rootRef?.(el);
        }}
        className={st(classes.root)}
        style={style}
      >
        {children}
      </div>
    </Transition>
  );
}

export const Collapse = observer(_Collapse);
