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

import type { YouTubeVideo as YoutubeVideoItem } from '@theorchard/songwhip-api';
import type { CSSProperties, FC } from 'react';
import type { TransitionInOut2Api } from '../TransitionInOut2';
import type {
  ChromelessYouTubeVideoApi,
  ChromelessYouTubeVideoProps,
  YouTubeVideoInfo,
} from './ChromelessYouTubeVideo';

import { isAndroid, isMobileBrowser, isWebView } from '~/lib/device/utils';
import { isTestEnv } from '~/lib/getSongwhipEnv';
import toSizedImageUrlNext from '~/src/lib/toSizedImageUrlNext';
import { useTracker } from '~/src/lib/tracker/useTracker';
import uuid from '~/src/lib/utils/uuid';
import AspectBox from '../AspectBox';
import Box from '../Box';
import Button from '../Button';
import FadeOnMount from '../FadeOnMount';
import Gradient from '../Gradient';
import { HydrationGuard } from '../HydrationGuard';
import PlayIcon from '../Icon/PlayIcon';
import Image from '../Image';
import Loading from '../Loading';
import Text from '../Text';
import TransitionInOut2 from '../TransitionInOut2';
import ChromelessYouTubeVideo from './ChromelessYouTubeVideo';
import { useGlobalMediaState } from './useGlobalMediaState';
import { toYouTubeLink } from './utils';
import { addViewportListeners } from './viewportController';

const _debug = Debug('songwhip/YouTubeVideo');

export type YouTubeVideoProps = {
  style?: CSSProperties;
  withOfficialControls?: boolean;
  withProgressBar?: boolean;
  image: YoutubeVideoItem['image'];
  title: string;
  priorityPreload?: boolean;
  backgroundColor?: string;
  isVisible?: boolean;

  /**
   * Begin playing the video (muted) when it enters the viewport.
   */
  autoplay?: boolean;
} & Pick<
  ChromelessYouTubeVideoProps,
  | 'width'
  | 'height'
  | 'aspect'
  | 'videoId'
  | 'muted'
  | 'loop'
  | 'containerAspect'
>;

const YouTubeVideo: FC<YouTubeVideoProps> = ({
  style,
  image,
  title,
  withOfficialControls,
  withProgressBar,
  autoplay,
  priorityPreload,
  isVisible,
  ...chromelessVideoProps
}) => {
  const rootElRef = useRef<HTMLDivElement>(null);
  const progressElRef = useRef<HTMLDivElement>(null);
  const playerApiRef = useRef<ChromelessYouTubeVideoApi>(null);
  const transitionCoverApiRef = useRef<TransitionInOut2Api>(null);
  const { width, height, videoId, aspect } = chromelessVideoProps;
  const [loadPlayer, setLoadPlayer] = useState(false);
  const [playerVisible, setPlayerVisible] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [isFocalItem, setIsFocalItem] = useState(false);
  const [error, setError] = useState<string>();

  const debug = useMemo(
    () => _debug.extend(chromelessVideoProps.videoId),
    [chromelessVideoProps.videoId]
  );

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const instanceId = useMemo(() => uuid(), [videoId]);
  const { trackEvent } = useTracker();
  const prevTrackedProgressRef = useRef(0);

  const imgStyle = {
    width: '102%',
    height: '102%',
    left: '-1%',
    top: '-1%',
    right: '-1%',
    bottom: '-1%',
    filter: 'saturate(0.8)',
  };

  if (image.aspect === '4:3') {
    imgStyle.width = imgStyle.height = '132%';
    imgStyle.top = imgStyle.left = '-16%';
  }

  const {
    getGlobalVideoPaused,
    globalVideoMuted,
    globalVideoPaused,
    dispatchGlobalVideoMutedChange,
    dispatchGlobalVideoStart,
    dispatchGlobalVideoPaused,
  } = useGlobalMediaState({
    instanceId,

    onOtherVideoStart: useCallback((playingInstanceId) => {
      debug('other video (%s) started', playingInstanceId);

      playerApiRef.current?.pause();

      if (!isMobileBrowser(navigator.userAgent)) {
        playerApiRef.current?.hideControls();
      }
    }, []),
  });

  useEffect(() => {
    const el = rootElRef.current;

    if (!el) {
      return;
    }

    return addViewportListeners({
      el,

      onViewportFocus() {
        debug('onViewportFocus');
        setIsFocalItem(true);
      },

      onViewportBlur() {
        debug('onViewportBlur');
        setIsFocalItem(false);

        // pause the video when it's no longer visible
        playerApiRef.current?.pause();

        // hide the controls so that if the video is paused
        // we don't see the "more videos" randomly appear
        playerApiRef.current?.hideControls();
      },
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [autoplay]);

  useEffect(() => {
    if ((isVisible === undefined || isVisible === true) && isFocalItem) {
      debug('is focal item', { isVisible, isFocalItem });

      // HACK: for test/dev purposes to interact with the current video
      if (isTestEnv()) {
        window['currentYoutubeVideo'] = playerApiRef.current;
      }

      if (autoplay && !getGlobalVideoPaused()) {
        playerApiRef.current?.play();
      }
    } else if (isVisible === false || !isFocalItem) {
      debug('is not focal item', { isVisible, isFocalItem });
      playerApiRef.current?.pause();
    }
  }, [isVisible, isFocalItem]);

  useEffect(() => {
    // keep the 'muted' preference in sync across all loaded players
    if (globalVideoMuted) {
      playerApiRef.current?.mute();
    } else {
      playerApiRef.current?.unmute();
    }
  }, [globalVideoMuted]);

  useEffect(() => {
    setIsLoading(true);

    // priority videos are loaded immediately internally this pushes
    // them onto the iframeLoadManager queue first
    if (priorityPreload) {
      debug('priority preload');
      setLoadPlayer(true);
      return;
    }

    // delaying other videos means they're pushed on the iframeLoadManager queue later
    setTimeout(() => {
      debug('normal preload');
      setLoadPlayer(true);
    }, 1000);
  }, []);

  return (
    <AspectBox
      nodeRef={rootElRef}
      aspect={aspect}
      containerAspect={aspect}
      width={width}
      height={height}
      testId="youtubeVideo"
      style={{
        background: '#000',
        width: '100%',
        ...style,
      }}
    >
      <TransitionInOut2
        isVisibleInitial
        apiRef={transitionCoverApiRef}
        coverParent
        duration={600}
        zIndex={1}
        style={{
          // COMPLEX: No clicks can ever land on the this cover artwork/play-button,
          // instead they end up hitting the youtube iframe behind. This is important
          // in the case of non-autoplay in android webview/in-app-browser when we can
          // only play the video with sound if the user has tapped the iframe. The click
          // lands on the iframe and triggers the video to play, once we detect that the video
          // is playing we hide the cover artwork to reveal the iframe player behind.
          pointerEvents: 'none',

          background: '#111',
        }}
      >
        {(() => {
          if (playerVisible) {
            return;
          }

          // don't show the loading spinners for autoplay videos
          if (autoplay && !globalVideoPaused) {
            return;
          }

          if (isLoading) {
            return <Loading coverParent size="4rem" zIndex={2} />;
          }

          return (
            <FadeOnMount>
              <Box coverParent zIndex={2}>
                <PlayIcon
                  size="6rem"
                  positionAbsolute
                  testId="playIcon"
                  left="50%"
                  top="50%"
                  zIndex={2}
                  margin="-3.5rem"
                  className="playIcon"
                  color="rgba(255,255,255,0.6)"
                  style={{
                    filter: 'drop-shadow(0 0 1.2rem #000)',
                  }}
                />
                <Gradient
                  positionAbsolute
                  top="60%"
                  bottom="-0.1%"
                  right="-0.1%"
                  left="-0.1%"
                  zIndex={2}
                  to="rgba(0,0,0,0.75)"
                  flexColumn
                  padding="1rem"
                  className="titleOverlay"
                  pointerEvents="none"
                >
                  <Text
                    margin="auto 0 0"
                    size="1.2rem"
                    centered
                    lineClamp={2}
                    shadow="0px 0px 0.5em #000"
                    lineHeight="1.2em"
                    opacity={0.9}
                  >
                    {title}
                  </Text>
                </Gradient>
              </Box>
            </FadeOnMount>
          );
        })()}
        <Image
          alt="Video thumbnail"
          src={toSizedImageUrlNext({
            url: image.url,
            width: 700,
          })}
          className="image"
          imgStyle={imgStyle}
          borderRadius="1.6rem"
          aspect={aspect}
          pointerEvents="none"
          isLazy
          style={{
            background: '#111',
          }}
        />
      </TransitionInOut2>
      <HydrationGuard>
        <ChromelessYouTubeVideo
          {...chromelessVideoProps}
          apiRef={playerApiRef}
          instanceId={instanceId}
          playOnLoad={false}
          shouldLoad={loadPlayer}
          withControls={withOfficialControls}
          muted={!!autoplay}
          onUserPlay={() => {
            debug('on user play');
          }}
          onUserPause={() => {
            debug('on user pause');
            dispatchGlobalVideoPaused();
          }}
          onReady={({ info }) => {
            debug('on ready', info);
            setIsLoading(false);

            // when the video is autoplay and the finishes loading we play it only if it's the focal item
            if (autoplay && isFocalItem) {
              playerApiRef.current?.play();
              return;
            }
          }}
          onStart={useCallback(
            ({ info, isFirstLoop }) => {
              setTimeout(() => {
                transitionCoverApiRef.current?.setVisible(false);
                setPlayerVisible(true);
              }, 100);

              if (isFirstLoop) {
                trackEvent({
                  type: 'video-start',
                  ...toBaseVideoTrackingParams(info),
                });
              }

              dispatchGlobalVideoStart();
            },
            [dispatchGlobalVideoStart, trackEvent]
          )}
          onError={(error) => {
            debug('on error', error);
            setError(error);
          }}
          onEnd={useCallback(({ info, isFirstLoop }) => {
            if (!isFirstLoop) return;

            trackEvent({
              type: 'video-complete',
              ...toBaseVideoTrackingParams(info),
            });
          }, [])}
          onTimeUpdate={useCallback(
            ({ durationSecs, elapsedSecs, info, isFirstLoop }) => {
              const progressEl = progressElRef.current;
              if (!progressEl) return;

              const percentageElapsed = (elapsedSecs / durationSecs) * 100;

              // disable the transition when video loops back to start
              progressEl.style.transition =
                percentageElapsed === 0 ? 'none' : 'transform 1s linear';

              progressEl.style.transform = `scaleX(${percentageElapsed / 100})`;

              const PROGRESS_CHECKPOINTS = [25, 50, 75, 90];
              const progressRounded = Math.round(percentageElapsed / 5) * 5;

              const isProgressCheckpoint =
                PROGRESS_CHECKPOINTS.includes(progressRounded);

              debug('on time update', {
                durationSecs,
                elapsedSecs,
                info,
                isFirstLoop,
                percentageElapsed,
                progressRounded,
              });

              // only track the progress on the first loop of the video to avoid endless events firing
              if (isProgressCheckpoint && isFirstLoop) {
                if (prevTrackedProgressRef.current === progressRounded) {
                  return;
                }

                debug('progress checkpoint: %s', progressRounded);

                trackEvent({
                  type: 'video-progress',
                  progress: progressRounded as 25 | 50 | 75 | 90,
                  ...toBaseVideoTrackingParams(info),
                });

                prevTrackedProgressRef.current = progressRounded;
              }
            },
            []
          )}
          onMutedChange={({ muted }) => {
            dispatchGlobalVideoMutedChange(muted);
          }}
        />
      </HydrationGuard>
      {error && (
        <FadeOnMount>
          <Box
            coverParent
            centerContent
            zIndex={2}
            padding="2rem"
            flexColumn
            testId="videoError"
            style={{
              background: '#111',
            }}
          >
            <Text
              centered
              lineHeight={1.3}
              size="1.8rem"
              maxWidth="40rem"
              balance
            >
              {`Error playing video (${error})`}
            </Text>
            <Button
              text="Watch on YouTube"
              height="4.8rem"
              margin="2rem 0 0"
              testId="watchOnYoutubeButton"
              isUppercase={false}
              href={toYouTubeLink(videoId)}
            />
          </Box>
        </FadeOnMount>
      )}
      {withProgressBar && (
        <Box positionAbsolute bottom={0} left={0} right={0} zIndex={1}>
          <Box
            positionAbsolute
            bottom={0}
            left={0}
            fullWidth
            style={{
              padding: style?.borderRadius ? '0 0.6rem' : 0,
            }}
          >
            <div
              style={{
                width: '100%',
                height: '.3rem',
                background: 'rgba(255,255,255,0)',
                boxShadow: '0 0 .4rem rgba(0,0,0,.13)',
              }}
            >
              <div
                ref={progressElRef}
                style={{
                  width: '100%',
                  transform: 'scaleX(0)',
                  transformOrigin: '0 0',
                  height: '100%',
                  background: 'rgba(255,255,255,1)',
                }}
              />
            </div>
          </Box>
        </Box>
      )}
    </AspectBox>
  );
};

const toBaseVideoTrackingParams = (info: YouTubeVideoInfo) => {
  const videoId = info.videoData?.video_id;

  return {
    id: `youtube:${videoId}`,
    provider: 'youtube' as const,
    title: info.videoData?.title || 'unknown',
    duration: info.duration || 0,
    url: videoId ? toYouTubeLink(videoId) : 'unknown',
  };
};

export const canUnmuteIframeBeforeInteraction = () => {
  return !(isAndroid(navigator.userAgent) && isWebView(navigator.userAgent));
};

export default YouTubeVideo;
