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

import type { ClickableOnClick } from '~/src/components/Clickable';
import type { TransitionInOut2Api } from '~/src/components/TransitionInOut2';
import type { RefObject } from 'react';
import type { PaginatedDotsApi } from '../lib/PaginatedDots';
import type { PageSectionComponent } from '../types';
import type { CarouselApi } from './lib/Carousel';
import type { CarouselSectionProps } from './types';

import TransitionInOut2 from '~/src/components/TransitionInOut2';
import useIsLargeScreen from '~/src/hooks/useIsLargeScreen';
import { toRgba } from '~/src/lib/utils/color';
import { usePageTheme } from '../../hooks/theme';
import PaginatedDots from '../lib/PaginatedDots';
import useOpenLink from '../lib/useOpenLink';
import { toCarouselFrameStyle } from './constants';
import Carousel from './lib/Carousel';
import CarouselSectionItem from './lib/CarouselSectionItem';

const PagePeekDynamic = dynamic(() => import('../lib/PagePeek'), {
  ssr: false,
});

const WindowIntersectionObserver =
  typeof window !== 'undefined' && window.IntersectionObserver;

// TODO: move this to hooks/ and use as base for other IntersectionObserver backed hooks
const useIntersectionObserver = ({
  elRef,
  callback,
}: {
  elRef: RefObject<HTMLElement | null>;
  callback: (visibleRatio: number) => void;
}) => {
  useEffect(() => {
    const { current: el } = elRef;

    // don't attempt to observe when browser lacks api
    if (!WindowIntersectionObserver) {
      callback(1);
      return;
    }

    if (!el) {
      return;
    }

    const observer = new WindowIntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          callback(entry.intersectionRatio);
        });
      },
      {
        threshold: [0.5],
      }
    );

    observer.observe(el);

    return () => {
      observer.unobserve(el);
      observer.disconnect();
    };
  }, [elRef.current]);
};

const CarouselSection: PageSectionComponent<CarouselSectionProps> = ({
  items,
}) => {
  const rootElRef = useRef<HTMLDivElement>(null);
  const carouselDotsApiRef = useRef<PaginatedDotsApi>(null);
  const transitionApiRef = useRef<TransitionInOut2Api>(null);
  const carouselApiRef = useRef<CarouselApi>(null);
  const isLargeScreen = useIsLargeScreen();
  const [pagePeekPath, setPagePeekPath] = useState<string>();
  const pageTheme = usePageTheme();
  const openLink = useOpenLink();

  useIntersectionObserver({
    elRef: rootElRef,
    callback: (value) => {
      transitionApiRef.current?.setVisible(value >= 0.5);
    },
  });

  const onDotClick = useCallback((index) => {
    carouselApiRef.current?.snapToIndex(index);
  }, []);

  const onItemClick = useCallback<
    ClickableOnClick<{ pagePeeking: boolean; text: string }>
  >(async ({ event, href, data: { pagePeeking, text } }) => {
    if (!href) return;
    event.preventDefault();

    openLink({
      url: href,
      pagePeeking,
      onPagePeek: setPagePeekPath,
      name: text,
    });
  }, []);

  // don't render anything when there is no content
  if (!items.length) {
    return null;
  }

  return (
    <div
      data-testid="carousel"
      style={{
        // ensure sections doesn't overlay header/gradient
        position: 'relative',
        zIndex: 0,

        willChange: 'opacity',
      }}
    >
      <div
        ref={rootElRef}
        style={{
          ...toCarouselFrameStyle({
            borderColor: toRgba(pageTheme.textColor, 0.4),
          }),
          margin: `0 ${isLargeScreen ? 1.2 : 1.6}rem 2rem`,
        }}
      >
        <TransitionInOut2
          apiRef={transitionApiRef}
          isVisibleInitial={false}
          duration={1200}
          styleFrom={{
            opacity: 0.7,
          }}
        >
          <Carousel
            items={items}
            apiRef={carouselApiRef}
            onChange={(index) => {
              carouselDotsApiRef.current?.setIndex(index);
            }}
            renderItem={({ item: { text, link, image, pagePeeking } }) => {
              return (
                <CarouselSectionItem
                  text={text}
                  href={link}
                  onClick={onItemClick}
                  image={image}
                  data={{ pagePeeking, text }}
                />
              );
            }}
          />
        </TransitionInOut2>
      </div>
      {items?.length > 1 && (
        <PaginatedDots
          total={items.length}
          apiRef={carouselDotsApiRef}
          onItemClick={onDotClick}
        />
      )}
      {pagePeekPath && (
        <PagePeekDynamic
          pagePath={pagePeekPath}
          onClose={() => {
            setPagePeekPath(undefined);
          }}
        />
      )}
    </div>
  );
};

export default CarouselSection;
