import * as React from 'react';
import { CSSProperties, useContext, useRef, useEffect, MouseEvent } from 'react';
import Arrow from '../../../Icons/Arrows/Arrow';
import { StoreContext } from '../../../../services/store';
import { IMedia } from '../../../../types';
import { uniformCarouselTransitionDurationSec } from '../../../consts';
import { useAccessibilityMessage } from '../../../../hooks/useAccessibilityMessage';
import {
  useUniformCenteredConfig,
  useCarouselLayout,
  useInfiniteNavigation,
} from '../shared/hooks';
import { useUniformTranslateX } from './hooks';
import {
  getCarouselMovementMessage,
  getSrOnlyStyle,
  normalizeIndex,
} from '../shared/accessibility';

interface Props<T> {
  items: T[];
  nbElems: number;
  style?: CSSProperties;
  onPostChange: (post: IMedia | null) => void;
}

const UniformCarousel = (props: Props<JSX.Element>) => {
  const context = useContext(StoreContext);
  const slideRef = useRef<HTMLDivElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const outerRef = useRef<HTMLDivElement>(null);

  const itemCount = props.items.length;
  const { accessibilityMessage, announce } = useAccessibilityMessage();
  const visibleContentSize = Math.max(props.nbElems, 1);
  const contentSize = context.data.settings.content_size;

  const { contentSizeNotReached, cloneCount } = useUniformCenteredConfig(
    itemCount,
    visibleContentSize,
    context.setStoreState,
  );

  const { compactStyle, carouselDimensions, arrowSize } = useCarouselLayout({
    context: {
      widgetWidth: context.widgetWidth,
      contentSize,
      isMobile: context.isMobile,
      hideArrows: context.config.hideArrows || false,
    },
    config: { contentSize: visibleContentSize, itemCount, partialItemWidth: 0 },
    refs: { slideRef },
  });

  const { activeIndex, handleSlide, skipTransition } = useInfiniteNavigation({
    config: { itemCount, contentSize: visibleContentSize, cloneCount, contentSizeNotReached },
    refs: { slideRef, containerRef },
    state: { itemWidth: carouselDimensions.itemWidth, currentPosition: context.currentPosition },
    actions: { setStoreState: context.setStoreState },
    transitionDurationSec: uniformCarouselTransitionDurationSec,
  });

  const { getTranslateX } = useUniformTranslateX({
    config: { cloneCount },
    state: { itemWidth: carouselDimensions.itemWidth, activeIndex },
  });

  const onArrowClick = (
    event: MouseEvent<HTMLButtonElement> | undefined,
    direction: 'left' | 'right',
  ) => {
    const nextIndex = activeIndex + (direction === 'left' ? -1 : 1);
    const normalizedIndex = normalizeIndex(nextIndex, itemCount);

    // Announce the next position for screen reader users, then perform the slide.
    announce(getCarouselMovementMessage(normalizedIndex, itemCount));
    handleSlide(direction);

    if (event) {
      context.triggerEvent(
        'carouselArrowClicked',
        {
          position: activeIndex,
          originalEvent: event.nativeEvent,
          direction,
        },
        context.data.id,
      );
    }
  };

  const renderSlides = () => {
    const { itemWidth } = carouselDimensions;
    if (cloneCount === 0) {
      return props.items.map((item, i) => renderSlide(item, i, itemWidth));
    }

    // Prepend and append clones so the carousel can scroll seamlessly.
    const cloneStart = props.items
      .slice(props.items.length - cloneCount)
      .map((item, i) => renderSlide(item, i - cloneCount, itemWidth));
    const realSlides = props.items.map((item, i) => renderSlide(item, i, itemWidth));
    const cloneEnd = props.items
      .slice(0, cloneCount)
      .map((item, i) => renderSlide(item, props.items.length + i, itemWidth));
    return [...cloneStart, ...realSlides, ...cloneEnd];
  };

  const renderSlide = (item: JSX.Element, index: number, width: number) => {
    const isClone = index < 0 || index >= itemCount;
    const cloneAccessibilityProps = isClone
      ? { 'aria-hidden': true, 'data-carousel-clone': 'true' }
      : {};

    // Clone slides are only for visual wrapping and should not be exposed to assistive tech.
    return (
      <div
        key={`slide-${index}`}
        style={{
          ...slideItemStyle,
          width,
          position: 'relative',
        }}
        {...cloneAccessibilityProps}
      >
        {item}
      </div>
    );
  };

  const renderDesktopCarousel = () => {
    // Hide arrows when there is only one item, when arrows are disabled, or when the content size limit is not yet reached.
    const showArrows = itemCount > 1 && !context.config.hideArrows && !contentSizeNotReached;
    const hasContentSizeNotReachedWithoutClones =
      cloneCount === 0 && itemCount < visibleContentSize;

    // When content size is not reached, shift items so the empty space is split evenly left/right
    const contentSizeNotReachedOffset = hasContentSizeNotReachedWithoutClones
      ? ((visibleContentSize - itemCount) * carouselDimensions.itemWidth) / 2
      : 0;
    const leftPosition = hasContentSizeNotReachedWithoutClones
      ? contentSizeNotReachedOffset
      : getTranslateX();

    return (
      <>
        <Arrow
          direction='left'
          disabled={false} // Infinite scroll
          onClick={(e) => onArrowClick(e, 'left')}
          show={showArrows}
          classPrefix={context.config.classPrefix}
          size={arrowSize}
          ariaLabel='Previous posts'
          customArrow={context.config.arrowLeft}
        />

        <div style={desktopSlideStyle} ref={slideRef}>
          <div
            ref={containerRef}
            style={{
              ...desktopSlideContainerStyle,
              transform: `translateX(${leftPosition}px)`,
              ...(skipTransition ? { transition: 'none' } : {}),
            }}
          >
            {renderSlides()}
          </div>
        </div>

        <Arrow
          direction='right'
          disabled={false} // Infinite scroll
          onClick={(e) => onArrowClick(e, 'right')}
          show={showArrows}
          classPrefix={context.config.classPrefix}
          size={arrowSize}
          ariaLabel='Next posts'
          customArrow={context.config.arrowRight}
        />
      </>
    );
  };

  return (
    <div
      className={`${context.config.classPrefix}-layout-carousel`}
      style={{
        ...carouselStyle,
        ...props.style,
        ...(compactStyle ?? {}),
      }}
      ref={outerRef}
    >
      <div aria-live='polite' aria-atomic='true' style={getSrOnlyStyle()}>
        {accessibilityMessage}
      </div>
      {renderDesktopCarousel()}
    </div>
  );
};

const carouselStyle: CSSProperties = {
  width: '100%',
  display: 'flex',
  position: 'relative',
  minHeight: 'fit-content',
  alignItems: 'center',
};

const desktopSlideStyle: CSSProperties = {
  flex: 1,
  height: 'auto',
  overflow: 'hidden',
  position: 'relative',
  display: 'flex',
  alignItems: 'center',
  paddingTop: '20px',
  paddingBottom: '20px',
};

const desktopSlideContainerStyle: CSSProperties = {
  position: 'relative',
  height: 'auto',
  display: 'flex',
  alignItems: 'center',
  willChange: 'transform',
  transition: `transform ${uniformCarouselTransitionDurationSec}s ease`,
};

const slideItemStyle: CSSProperties = {
  display: 'inline-block',
  height: 'auto',
  flexShrink: 0,
  boxSizing: 'border-box',
  isolation: 'auto',
};

export default UniformCarousel;
