import { createElement, useCallback, useEffect, useRef, useState } from 'react';
import Debug from 'debug';

import type { WithSpacingProps } from '~/src/lib/hocs/withSpacing';
import type { CSSProperties, HTMLAttributes } from 'react';

import withSpacing from '~/src/lib/hocs/withSpacing';
import { caf, raf } from '~/src/lib/utils/raf';
import { stripNonDomProps } from '~/src/lib/utils/stripNonDomProps';

const debug = Debug('songwhip/components/TransitionInOut');

interface TransitionInOutProps
  extends WithSpacingProps,
    HTMLAttributes<HTMLDivElement> {
  tag?: string;
  isVisible?: boolean;
  willChange?: boolean;
  duration?: number;
  delay?: number;
  styleFrom?: CSSProperties;
  onHidden?: () => void;
  transitionOnMount?: boolean;
  style?: CSSProperties;
  role?: string;
}

const TransitionInOut = withSpacing<TransitionInOutProps>(
  ({
    tag = 'div',
    isVisible = true,
    willChange = false,
    duration = 400,
    delay = 0,
    className,
    children,
    styleFrom,
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    onHidden = () => {},
    transitionOnMount = true,
    style,
    role,
    ...props
  }) => {
    const styleFromInternal = {
      opacity: 0,
      ...styleFrom,
    };

    const styleToInternal = {
      opacity: 1,
      transform: 'none',
    };

    // don't attempt to transition when duration is 0, this means children are
    // rendered of first paint avoiding flash on second frame when raf hits
    if (duration === 0) {
      transitionOnMount = false;
    }

    const getNextStyle = () =>
      isVisible ? styleToInternal : styleFromInternal;

    const initialStyleDynamic = !transitionOnMount ? getNextStyle() : {};
    const [styleDynamic, setStyle] = useState(initialStyleDynamic);
    const animationFrameRef = useRef(0);

    const styleInternal = {
      ...style,
      transitionProperty: 'opacity,transform',
      transitionDuration: `${duration}ms`,
      transitionDelay: `${delay}ms`,
      ...styleFromInternal,
      ...styleDynamic,
    };

    if (willChange) {
      styleInternal.willChange = 'opacity';
    }

    const onTransitionEnd = useCallback(() => {
      if (!isVisible) {
        debug('is hidden');
        onHidden();
      }
    }, [isVisible, onHidden]);

    // on mount & isVisible change
    useEffect(() => {
      const nextStyle = getNextStyle();

      animationFrameRef.current = raf(() => {
        animationFrameRef.current = raf(() => {
          setStyle(nextStyle);
        });
      });

      // on unmount
      return () => {
        caf(animationFrameRef.current);
      };
    }, [isVisible]);

    return createElement(
      tag,
      {
        className,
        onTransitionEnd,
        style: styleInternal,
        role,
        ...stripNonDomProps(props),
      },
      children
    );
  }
);

export default TransitionInOut;
