import { useState, useEffect, useRef } from 'react';
import { Skeleton } from '@mui/material';

import PropTypes from 'prop-types';
import Lottie from 'react-lottie-player';
import noop from 'lodash/noop';

import { styled, useTheme } from '../Theme';

/**
 * 用于长时间等待的用的动画组件
 * 动画会随着时间的变化而逐步加快播放速度，好适应用户的等待心理
 * @param {object} props - 组件属性
 * @param {object} [props.animationData] - lottie json 动画数据
 * @param {number|string} [props.size] - 动画的尺寸，单位px
 * @param {string|React.ReactNode[]} [props.message] - 动画下方的文字；数组情况下会在一定时间切换文案
 * @param {number} [props.messageDuration=5000] - 动画下方的文字为数组时，每个文案的持续时间（毫秒）
 * @param {boolean} [props.messageLoop=true] - 动画下方的文字为数组时，文案是否循环播放
 * @param {React.ReactNode[]} [props.tips=[]] - 底部的提示元素
 * @param {number} [props.tipsDuration=5000] - 底部提示的切换时间（毫秒）
 * @param {number} [props.speed=1] - 动画默认的播放速度
 * @param {number} [props.maybeDuration=120000] - 整个动画大概的持续时间（毫秒），用于计算增量下的动画速度
 * @param {number} [props.increaseSpeed=0] - 在 maybeDuration 时间下增加的速度
 * @returns {JSX.Element}
 */
export default function AnimationWaiter({
  animationData = null,
  size = 256,
  message = '',
  messageDuration = 5000,
  messageLoop = true,
  tips = [],
  tipsDuration = 5000,
  maybeDuration = 120000,
  speed = 1,
  increaseSpeed = 0,
  ...rest
}) {
  const theme = useTheme();
  const [tipsId, setTipsId] = useState(0);
  const [currentSpeed, setCurrentSpeed] = useState(speed);
  const [messageId, setMessageId] = useState(0);
  const [defaultAnimation, setDefaultAnimation] = useState(null);
  const [isLoading, setIsLoading] = useState(!animationData);
  // 动画的开始时间
  const startTime = useRef(new Date().getTime());

  if (!Array.isArray(message)) {
    // eslint-disable-next-line no-param-reassign
    message = [message];
  }

  // 懒加载默认动画
  useEffect(() => {
    if (!animationData) {
      setIsLoading(true);
      const loadAnimation = async () => {
        try {
          const module = await (theme.palette.mode === 'dark'
            ? import('./dark-animation.json')
            : import('./default-animation.json'));

          setDefaultAnimation(module.default || module);

          setIsLoading(false);
        } catch (error) {
          console.error('Failed to load animation:', error);
          setIsLoading(false);
        }
      };

      loadAnimation();
    }
  }, [animationData, theme.palette.mode]);

  useEffect(() => {
    if (!message) {
      return;
    }

    let msgIndex = 0;

    const timer = setInterval(() => {
      msgIndex++;
      if (msgIndex >= message.length) {
        if (messageLoop) {
          msgIndex = 0;
        } else {
          msgIndex = message.length - 1;
        }
      }
      setMessageId(msgIndex);
    }, messageDuration);

    // eslint-disable-next-line consistent-return
    return () => {
      clearInterval(timer);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [message, messageDuration]);

  // tips
  useEffect(() => {
    if (!tips.length) {
      return noop;
    }

    const timer = setTimeout(() => {
      let nextId = tipsId + 1;

      if (nextId >= tips.length) {
        nextId = 0;
      }

      setTipsId(nextId);
    }, tipsDuration);

    return () => clearTimeout(timer);
  }, [tips, tipsDuration, tipsId]);

  // 动画speed
  useEffect(() => {
    const timer = setTimeout(() => {
      const diffTime = new Date().getTime() - startTime.current;

      let percentage = diffTime / maybeDuration;
      if (percentage > 1) {
        percentage = 1;
      }

      const newSpeed = speed + increaseSpeed * percentage;

      setCurrentSpeed(newSpeed);
    }, 1000);

    return () => clearTimeout(timer);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentSpeed]);

  const getMsgClassName = (index) => {
    let className = 'message-before';
    if (messageId === index) {
      className = 'show-message';
    } else if (messageId < index) {
      className = 'message-after';
    }
    return className;
  };

  return (
    <Container {...rest}>
      {isLoading ? (
        <div style={{ width: size, height: size, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          <Skeleton variant="rounded" width={size * 0.4} height={size * 0.4} />
        </div>
      ) : (
        <Lottie
          loop
          animationData={animationData || defaultAnimation}
          play
          speed={currentSpeed}
          style={{
            width: size,
            height: size,
          }}
        />
      )}
      {message && (
        <div className="waiter-message">
          {message.map((text, index) => {
            const className = getMsgClassName(index);
            return (
              <div
                // eslint-disable-next-line react/no-array-index-key
                key={index}>
                <span className={`message-block ${className}`}>{text}</span>
                <span className={`placeholder-message ${className}`}>{text}</span>
              </div>
            );
          })}
        </div>
      )}

      {tips.length ? (
        <div className="waiter-tips-container">
          {tips.map((e, index) => {
            return (
              <div
                className={`waiter-tips-block ${tipsId === index ? 'show-tips' : ''}`}
                // eslint-disable-next-line react/no-array-index-key
                key={index}>
                {e}
              </div>
            );
          })}
        </div>
      ) : (
        ''
      )}
    </Container>
  );
}

AnimationWaiter.propTypes = {
  animationData: PropTypes.oneOfType([PropTypes.object, PropTypes.oneOf([null])]),
  size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.node)]),
  messageDuration: PropTypes.number,
  messageLoop: PropTypes.bool,
  tips: PropTypes.arrayOf(PropTypes.node),
  tipsDuration: PropTypes.number,
  speed: PropTypes.number,
  maybeDuration: PropTypes.number,
  increaseSpeed: PropTypes.number,
};

const Container = styled('div')`
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  width: 100%;
  height: 100%;

  .waiter-message {
    position: relative;
    width: 100%;
    font-weight: 700;
    font-size: 18px;
    line-height: 22px;
    text-align: center;
    overflow: hidden;
    .message-block {
      position: absolute;
      left: 0;
      width: 100%;
      opacity: 0;
      transition: all ease 0.3s;
      user-select: none;
      &.message-before {
        transform: translate(-20px, 0);
      }
      &.message-after {
        transform: translate(20px, 0);
      }
      &.show-message {
        transform: translate(0, 0);
        opacity: 1;
        user-select: text;
        z-index: 2;
      }
    }

    .placeholder-message {
      user-select: none;
      display: none;
      opacity: 0;
      &.show-message {
        display: block;
      }
    }
  }

  .waiter-tips-container {
    position: relative;
    margin-top: auto;
    width: 100%;
    .waiter-tips-block {
      position: absolute;
      bottom: 0;
      left: 0;
      width: 100%;
      opacity: 0;
      pointer-events: none;
      z-index: 1;
      transform: translate(-20px, 0);
      transition: all ease 0.4s;
      &.show-tips {
        opacity: 1;
        pointer-events: auto;
        z-index: 2;
        transform: translate(0, 0);
      }
    }
  }
`;
