import React, { memo, useEffect, useState, useRef, FC } from 'react';
import * as d3 from 'd3';
import { useDebouncedValue } from 'components/easyUse';
import { isGroup } from 'utils/utils';

const defaultAnimation = {
  key: 'show',
  from: {
    opacity: 0
  },
  to: {
    opacity: 1
  },
  config: {
    duration: 600,
    timingFunction: 'ease',
    delay: 0
  }
};

const Animation: FC<{
  id: string | number;
  iState?: any;
  iActiveState?: any;
  width: number;
  height: number;
  top: number;
  left: number;
  style?: React.CSSProperties;
  children: React.ReactElement;
}> = ({ id, children, iState, iActiveState = {}, width = 0, height = 0, top = 0, left = 0, style = {} }) => {
  const [opacity, setOpacity] = useState(() => {
    return iState ? (iState.show === false ? 0 : 1) : 1;
  });
  const [hidden, setHidden] = useState<boolean>(false);
  const componentRef = useRef(null);

  const {
    animation: { key, from, to, config: { duration = 600, timingFunction = 'ease' } = { ...defaultAnimation.config } } = {
      ...defaultAnimation
    }
  } = iActiveState;
  const active = iState && iState[key];

  useEffect(() => {
    const visibilitychange = () => {
      setHidden(document.hidden);
    };
    document.addEventListener('visibilitychange', visibilitychange);
    return () => {
      document.removeEventListener('visibilitychange', visibilitychange);
    };
  }, []);

  useEffect(() => {
    // 当前浏览器tab必须是激活状态
    if (iState && !hidden) {
      const nextOpacity = iState.show === false ? 0 : 1;
      if (nextOpacity !== opacity) {
        componentRef.current &&
          d3
            .select(componentRef.current)
            ?.transition?.()
            .duration(duration)
            .tween('attr.opacity', () => {
              const interpolates = d3.interpolateNumber(opacity, nextOpacity);
              return (t) => {
                setOpacity(interpolates(t));
              };
            });
      }
    }
  }, [active, hidden]);

  // display:none 立即更新组件最终状态，动画不会执行
  const status = useDebouncedValue(iState ? (iState[key] ? to : from) : {}, 20) || {};

  if (!iState) {
    return children || null;
  }

  // eslint-disable-next-line no-unused-vars
  const { visibility, ...restStatus } = status;

  const unmount = iState.unmount && opacity === 0;

  const nextStatus = isGroup(id + '') ? {} : getNextStatus(iState, { x: left, y: top });

  return (
    <div
      ref={componentRef}
      style={{
        ...style,
        pointerEvents: 'none',
        position: 'absolute',
        top,
        left,
        width,
        height,
        ...restStatus,
        ...nextStatus,
        display: opacity === 0 ? 'none' : 'block',
        opacity: opacity === 1 || opacity === 0 ? undefined : opacity,
        transition: `transform ${duration}ms ${timingFunction}`
      }}>
      <div style={{ top: -top, left: -left, position: 'absolute' }}>{unmount ? null : children}</div>
    </div>
  );
};

function getNextStatus(iState, position) {
  const { translateToX, translateToY, scaleX, scaleY, transformOrigin, rotate } = iState;
  const transformValues: string[] = [];
  let transformOriginValue;
  if (translateToX != null || translateToY != null) {
    transformValues.push(`translate3d(${translateToX - position.x}px, ${translateToY - position.y}px, 0px)`);
  }
  if (scaleX != null || scaleY != null) {
    transformValues.push(`scaleX(${scaleX}) scaleY(${scaleY})`);
    transformOriginValue = transformOrigin;
  }
  if (rotate && (rotate.rotateX != null || rotate.rotateY != null || rotate.rotateZ != null)) {
    const { rotateX = 0, rotateY = 0, rotateZ = 0, perspective } = rotate;
    transformValues.push(`rotateX(${rotateX}deg) rotateY(${rotateY}deg) rotateZ(${rotateZ}deg)`);
    if (perspective) {
      transformValues.unshift('perspective(500px)');
    }
  }

  if (transformValues.length > 0) {
    return {
      transform: transformValues.join(' '),
      transformOrigin: transformOriginValue
    };
  } else {
    return {};
  }
}

export default memo(Animation);
