import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import dynamic from 'next/dynamic';

import type { AudioHook } from '~/src/hooks/useAudio';
import type { PaginatedDotsApi } from '../../lib/PaginatedDots';
import type { PageSectionComponent } from '../../types';
import type { StoriesMedia, StoriesSectionProps } from '../types';

import { toNumber } from '~/lib/utils/number';
import Box from '~/src/components/Box';
import Clickable from '~/src/components/Clickable';
import PlayIcon from '~/src/components/Icon/PlayIcon';
import WarningIcon from '~/src/components/Icon/WarningIcon';
import BackgroundImage from '~/src/components/Image/BackgroundImage';
import Loading from '~/src/components/Loading';
import HorizontalScroller from '~/src/components/Scroller2/HorizontalScroller';
import Text from '~/src/components/Text';
import { useGlobalMediaState } from '~/src/components/YouTubeVideo/useGlobalMediaState';
import useDebounce from '~/src/hooks/useDebounce';
import useHash from '~/src/hooks/useHash';
import { useHlsAudio } from '~/src/hooks/useHlsAudio';
import useIsLargeScreen from '~/src/hooks/useIsLargeScreen';
import { toRgba } from '~/src/lib/utils/color';
import onceIdle from '~/src/lib/utils/onceIdle';
import uuid from '~/src/lib/utils/uuid';
import { usePageTheme } from '../../../hooks/theme';
import PaginatedDots from '../../lib/PaginatedDots';
import { DEFAULT_IMAGE_URL } from '../constants';
import { getTranscodedItems } from '../utils';
import { toDialogHashParam } from './utils';

const MediaDialogLazy = dynamic(() => import('./components/MediaDialog'), {
  ssr: false,
});

const StoriesSectionView: PageSectionComponent<
  Required<StoriesSectionProps>
> = ({ sectionPath, items }) => {
  const scrollerRef = useRef<HTMLDivElement>(null);
  const dotsRef = useRef<PaginatedDotsApi>(null);

  const { setHashParam, getHashParam } = useHash();
  const isLargeScreen = useIsLargeScreen();

  const dialogHashParam = toDialogHashParam(sectionPath);
  const dialogHashValue = toNumber(getHashParam(dialogHashParam));

  const dialogItemId =
    dialogHashValue !== undefined && Boolean(items[dialogHashValue])
      ? dialogHashValue
      : undefined;

  const [activeItemId, setActiveItemId] = useState(dialogItemId ?? 0);
  const currentItemId = dialogItemId ?? activeItemId;

  const { audio, error: audioError } = useHlsAudio(items[currentItemId]);

  const audioRef = useRef(audio);
  audioRef.current = audio;

  const instanceId = useMemo(() => uuid(), []);

  const { dispatchGlobalVideoStart } = useGlobalMediaState({
    instanceId,
    onOtherVideoStart: useCallback(() => {
      audioRef.current.pause();
    }, []),
  });

  const scrollTo = useCallback(
    (index: number, behavior: ScrollBehavior) => {
      const scroller = scrollerRef.current;

      if (!scroller || !items.length) return;
      if (index < 0 || index > items.length - 1) return;

      scroller.scrollTo({
        behavior: behavior,
        left: index * Math.round(scroller.scrollWidth / items.length),
      });
    },
    [items.length]
  );

  const onScrollDebounced = useDebounce(
    ({ x, maxX }) => {
      const itemWidth = maxX / (items.length - 1);
      const itemIndex = Math.round(x / itemWidth);

      setActiveItemId(itemIndex);
      dotsRef.current?.setIndex(itemIndex);
    },
    100,
    [items.length]
  );

  // sync active items between dialog active item and section active item
  useEffect(() => {
    if (dialogItemId === undefined) return;

    return onceIdle(() => {
      scrollTo(dialogItemId, 'instant');
    });
  }, [dialogItemId]);

  return (
    <>
      <Box
        testId="storiesSection"
        padding={`0 ${isLargeScreen ? '1.2rem' : '1.6rem'}`}
      >
        <Box positionRelative>
          <HorizontalScroller
            zIndex={1}
            withSnapping
            scrollerRef={scrollerRef}
            gradientColor="transparent"
            overflowStyle={isLargeScreen ? 'outside' : undefined}
            forceOverflowing={items.length > 1}
            scrollerStyle={{
              border: '1px solid #333',
              borderRadius: '1rem',
            }}
            contentStyle={{
              minWidth: 'calc(100% + 2px)', // compensate for the border
              margin: '-1px', // compensate for the border
            }}
            onScroll={onScrollDebounced}
            renderContent={({ snapItemStyle }) =>
              items.map((item, index) => {
                const isActiveItem = index === currentItemId;
                const activeItemState = audioError ? 'ERROR' : audio.state;

                return (
                  <Box
                    key={`${item.resourceId}-${index}`}
                    testId="storiesSectionItem"
                    minWidth="100%"
                    height={isLargeScreen ? '45.6rem' : '40rem'}
                    style={snapItemStyle}
                    noFlexShrink
                  >
                    <MediaItem
                      {...item}
                      audioState={isActiveItem ? activeItemState : 'IDLE'}
                      onClick={() => {
                        setHashParam({ [dialogHashParam]: `${index}` });

                        // stop any playing videos
                        dispatchGlobalVideoStart();

                        // we need to start playing audio synchronously on trusted user action
                        audio.play();
                      }}
                    />
                  </Box>
                );
              })
            }
          />
        </Box>
        {items.length > 1 && (
          <Box testId="storiesSectionPagination" padding="1rem">
            <PaginatedDots
              apiRef={dotsRef}
              total={items.length}
              onItemClick={(index) => scrollTo(index, 'smooth')}
            />
          </Box>
        )}
      </Box>
      {dialogItemId !== undefined && (
        <MediaDialogLazy
          hashParam={dialogHashParam}
          itemId={dialogItemId}
          items={items}
          audio={audio}
          audioError={audioError}
        />
      )}
    </>
  );
};

const MediaItem = ({
  audioState,
  onClick,

  ...item
}: {
  audioState: AudioHook['state'];
  onClick(): void;
} & StoriesMedia) => {
  const { title, imageUrl = DEFAULT_IMAGE_URL } = item;

  const pageTheme = usePageTheme();

  const content = useMemo(() => {
    switch (audioState) {
      case 'IDLE':
      case 'LOADING':
        return <Loading size="4rem" />;

      case 'ERROR':
        return <WarningIcon size="6rem" />;

      // even if we could possibly have an error
      // we still want to show the play button, so user can retry
      default:
        return (
          <Clickable
            testId="storiesSectionItemPlay"
            fullHeight
            flexBox
            centerContent
            onClick={onClick}
          >
            <PlayIcon color="rgba(255, 255, 255, 0.8)" size="8rem" />
          </Clickable>
        );
    }
  }, [audioState]);

  return (
    <Box zIndex={1} positionRelative fullWidth fullHeight>
      <BackgroundImage
        coverParent
        zIndex={-1}
        src={imageUrl}
        gradient={`
          linear-gradient(0deg,
            ${toRgba(pageTheme.backgroundColor, 1)} 0%,
            ${toRgba(pageTheme.backgroundColor, 0)} 20%,
            ${toRgba(pageTheme.backgroundColor, 0)} 80%,
            ${toRgba(pageTheme.backgroundColor, 1)} 100%
          )`}
      />
      <Box zIndex={1} coverParent flexBox centerContent>
        {content}
      </Box>
      <Text
        zIndex={2}
        positionAbsolute
        bottom="2.4rem"
        fullWidth
        size="1.3rem"
        lineHeight="1.7rem"
        padding="0 2rem"
        color={pageTheme.textColor}
        withEllipsis
        centered
      >
        {title}
      </Text>
    </Box>
  );
};

export const StoriesSection: PageSectionComponent<StoriesSectionProps> = (
  props
) => {
  const { items = [] } = props;

  // Filter to only show transcoding-complete items
  const transcodedItems = getTranscodedItems(items);

  if (transcodedItems.length > 0) {
    return <StoriesSectionView {...props} items={transcodedItems} />;
  }

  // don't render anything if there are no items
  return null;
};
