import React, {
  CSSProperties,
  MutableRefObject,
  ReactElement,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { CSSTransition, SwitchTransition } from 'react-transition-group';
import classNames from 'classnames';
import RadialProgress from './RadialProgress';
import {
  NO_RANK,
  rankToColor,
  rankToGradientStopBottom,
  rankToGradientStopTop,
  getNextRankText,
  isFinalRank,
  RANKS,
  getRank,
} from '../lib/rank';
import Rank from './Rank';
import styles from './RankProgress.module.css';

const notificationDuration = 300;

interface GetRankElementProps {
  shownRank: number;
  rank: number;
  badgeRef: MutableRefObject<SVGSVGElement>;
  plusRef: MutableRefObject<HTMLElement>;
  smallVersion: boolean;
  showRankAnimation: boolean;
  animatingProgress: boolean;
}

type RankElementProps = Pick<
  GetRankElementProps,
  'shownRank' | 'rank' | 'badgeRef'
>;
const RankElement = ({
  shownRank,
  rank,
  badgeRef,
}: RankElementProps): ReactElement => (
  <Rank
    rank={shownRank}
    className={classNames(
      'absolute inset-0 w-2/3 h-2/3 m-auto',
      styles.rank,
      shownRank && styles.hasRank,
    )}
    ref={badgeRef}
    colorByRank={rank > 0}
  />
);

const getRankElement = ({
  shownRank,
  rank,
  badgeRef,
  plusRef,
  smallVersion,
  showRankAnimation,
  animatingProgress,
}: GetRankElementProps): ReactElement => {
  const animating = showRankAnimation || animatingProgress;
  if (!smallVersion || !animating) {
    return (
      <RankElement shownRank={shownRank} rank={rank} badgeRef={badgeRef} />
    );
  }

  return (
    <strong
      ref={plusRef}
      className="flex absolute inset-0 justify-center items-center text-theme-rank typo-callout"
    >
      +1
    </strong>
  );
};

export type RankProgressProps = {
  progress: number;
  rank: number;
  nextRank?: number;
  showRankAnimation?: boolean;
  showCurrentRankSteps?: boolean;
  className?: string;
  onRankAnimationFinish?: () => unknown;
  fillByDefault?: boolean;
  smallVersion?: boolean;
  showRadialProgress?: boolean;
  showTextProgress?: boolean;
  rankLastWeek?: number;
};

const getRankName = (rank: number): string =>
  rank > 0 ? RANKS[rank - 1].name : NO_RANK;

export function RankProgress({
  progress,
  rank = 0,
  nextRank,
  showRankAnimation = false,
  showCurrentRankSteps = false,
  className,
  onRankAnimationFinish,
  fillByDefault = false,
  smallVersion = false,
  showRadialProgress = true,
  showTextProgress = false,
  rankLastWeek,
}: RankProgressProps): ReactElement {
  const [prevProgress, setPrevProgress] = useState(progress);
  const [animatingProgress, setAnimatingProgress] = useState(false);
  const [forceColor, setForceColor] = useState(false);
  const [shownRank, setShownRank] = useState(
    showRankAnimation ? getRank(rank) : rank,
  );
  const attentionRef = useRef<HTMLDivElement>();
  const progressRef = useRef<HTMLDivElement>();
  const badgeRef = useRef<SVGSVGElement>();
  const plusRef = useRef<HTMLElement>();

  const rankIndex = getRank(rank);
  const finalRank = isFinalRank(rank);
  const levelUp = () =>
    rank > shownRank &&
    rank > 0 &&
    (rank !== rankLastWeek || progress === RANKS[rankIndex].steps);
  const getLevelText = levelUp() ? 'You made it 🏆' : '+1 Reading day';
  const shouldForceColor = animatingProgress || forceColor || fillByDefault;

  const steps = useMemo(() => {
    if (
      showRankAnimation ||
      showCurrentRankSteps ||
      (finalRank && progress < RANKS[rankIndex].steps)
    ) {
      return RANKS[rankIndex].steps;
    }

    if (!finalRank) {
      return RANKS[rank].steps;
    }
    return 0;
  }, [showRankAnimation, showCurrentRankSteps, shownRank, progress, rank]);

  const animateRank = () => {
    setForceColor(true);
    const animatedRef = badgeRef.current || plusRef.current;
    const firstAnimationDuration = 400;
    const maxScale = 1.666;

    const progressAnimation = showRadialProgress
      ? progressRef.current.animate(
          [
            {
              transform: 'scale(1) rotate(180deg)',
              '--radial-progress-completed-step': rankToColor(shownRank),
            },
            { transform: `scale(${maxScale}) rotate(180deg)` },
            {
              transform: 'scale(1) rotate(180deg)',
              '--radial-progress-completed-step': rankToColor(rank),
            },
          ],
          { duration: firstAnimationDuration, fill: 'forwards' },
        )
      : null;

    const firstBadgeAnimation = animatedRef.animate(
      [
        {
          transform: 'scale(1)',
          '--stop-color1': rankToGradientStopBottom(shownRank),
          '--stop-color2': rankToGradientStopTop(shownRank),
        },
        { transform: `scale(${maxScale})`, opacity: 1 },
        { transform: 'scale(1)', opacity: 0 },
      ],
      { duration: firstAnimationDuration, fill: 'forwards' },
    );
    firstBadgeAnimation.onfinish = () => {
      setShownRank(rank);
      // Let the new rank update
      setTimeout(() => {
        const attentionAnimation = showRankAnimation
          ? attentionRef.current.animate(
              [
                {
                  transform: 'scale(0.5)',
                  opacity: 1,
                },
                {
                  transform: 'scale(1.5)',
                  opacity: 0,
                },
              ],
              { duration: 600, fill: 'forwards' },
            )
          : null;

        const lastBadgeAnimation = animatedRef.animate(
          [
            {
              transform: `scale(${2 - maxScale})`,
              opacity: 0,
              '--stop-color1': rankToGradientStopBottom(rank),
              '--stop-color2': rankToGradientStopTop(rank),
            },
            {
              transform: 'scale(1)',
              opacity: 1,
              '--stop-color1': rankToGradientStopBottom(rank),
              '--stop-color2': rankToGradientStopTop(rank),
            },
          ],
          { duration: 100, fill: 'forwards' },
        );
        const cancelAnimations = () => {
          progressAnimation?.cancel();
          firstBadgeAnimation.cancel();
          lastBadgeAnimation?.cancel();
          attentionAnimation?.cancel();
          setForceColor(false);
          onRankAnimationFinish?.();
        };

        if (attentionAnimation) {
          attentionAnimation.onfinish = cancelAnimations;
        } else {
          cancelAnimations();
          setTimeout(() => setAnimatingProgress(false), 2000);
        }
      });
    };
  };

  const onProgressTransitionEnd = () => {
    if (showRankAnimation || levelUp()) {
      setAnimatingProgress(false);
      animateRank();
    } else {
      setTimeout(() => setAnimatingProgress(false), 2000);
    }
  };

  useEffect(() => {
    if (!showRankAnimation) {
      setShownRank(rank);
    }
  }, [rank]);

  useEffect(() => {
    if (
      progress > prevProgress &&
      (!rank ||
        showRankAnimation ||
        levelUp() ||
        RANKS[rankIndex].steps !== progress)
    ) {
      if (!showRadialProgress) animateRank();
      setAnimatingProgress(true);
    }
    setPrevProgress(progress);
  }, [progress]);

  return (
    <>
      <div
        className={classNames(
          className,
          'relative z-1 border-1',
          styles.rankProgress,
          {
            [styles.enableColors]: shownRank > 0,
            [styles.forceColor]: shouldForceColor,
            [styles.smallVersion]: smallVersion && showRadialProgress,
            [styles.smallVersionClosed]: smallVersion && !showRadialProgress,
          },
        )}
        style={
          shownRank > 0
            ? ({
                '--rank-color': rankToColor(shownRank),
                '--rank-stop-color1': rankToGradientStopBottom(shownRank),
                '--rank-stop-color2': rankToGradientStopTop(shownRank),
              } as CSSProperties)
            : {}
        }
      >
        {showRankAnimation && (
          <div
            className="absolute -z-1 w-full h-full bg-theme-active rounded-full opacity-0 l-0 t-0"
            ref={attentionRef}
          />
        )}
        {showRadialProgress && (
          <RadialProgress
            progress={progress}
            steps={steps}
            onTransitionEnd={onProgressTransitionEnd}
            ref={progressRef}
            className={styles.radialProgress}
          />
        )}

        <SwitchTransition mode="out-in">
          <CSSTransition
            timeout={notificationDuration}
            key={animatingProgress}
            classNames="rank-notification-slide-down"
            mountOnEnter
            unmountOnExit
          >
            {getRankElement({
              shownRank,
              rank,
              badgeRef,
              plusRef,
              smallVersion,
              showRankAnimation,
              animatingProgress,
            })}
          </CSSTransition>
        </SwitchTransition>
      </div>
      {showTextProgress && (
        <SwitchTransition mode="out-in">
          <CSSTransition
            timeout={notificationDuration}
            key={animatingProgress}
            classNames="rank-notification-slide-down"
            mountOnEnter
            unmountOnExit
          >
            <div className="flex flex-col items-start ml-3">
              <span className="font-bold text-theme-rank typo-callout">
                {animatingProgress ? getLevelText : getRankName(shownRank)}
              </span>
              <span className="typo-footnote text-theme-label-tertiary">
                {getNextRankText({
                  nextRank,
                  rank,
                  finalRank,
                  progress,
                  rankLastWeek,
                  showNextLevel: !finalRank,
                })}
              </span>
            </div>
          </CSSTransition>
        </SwitchTransition>
      )}
    </>
  );
}