import { ChevronLeft, ChevronRight } from '@transferwise/icons';
import { clsx } from 'clsx';
import { type CSSProperties, type ReactNode, useEffect, useRef, useState } from 'react';

import IconButton from '../iconButton';
import Title from '../title';
import type { PromoCardLinkProps } from '../promoCard/PromoCard';
import PromoCard from '../promoCard/PromoCard';

export type CarouselCardCommon = {
  id: string;
  href?: string;
  hrefTarget?: HTMLAnchorElement['target'];
  onClick?: () => void;
  className?: string;
  styles?: CSSProperties;
};

export type CarouselDefaultCard = CarouselCardCommon & {
  type: 'anchor' | 'button';
  content: ReactNode;
};

export type CarouselPromoCard = CarouselCardCommon & {
  type: 'promo';
} & Omit<PromoCardLinkProps, 'type'>;

export type CarouselCard = CarouselDefaultCard | CarouselPromoCard;
export interface CarouselProps {
  header: string | ReactNode;
  className?: string;
  cards: CarouselCard[];
  onClick?: (cardId: string) => void;
}

type CardsReference = {
  type: 'promo' | 'default';
  cardElement: HTMLDivElement | HTMLAnchorElement;
  anchorElement?: HTMLAnchorElement;
};

const LEFT_SCROLL_OFFSET = 8;

const Carousel: React.FC<CarouselProps> = ({ header, className, cards, onClick }) => {
  const [scrollPosition, setScrollPosition] = useState(0);
  const [previousScrollPosition, setPreviousScrollPosition] = useState(0);
  const [scrollIsAtEnd, setScrollIsAtEnd] = useState(false);
  const [visibleCardOnMobileView, setVisibleCardOnMobileView] = useState<string>('');
  const carouselElementRef = useRef<HTMLDivElement>(null);
  const carouselCardsRef = useRef<CardsReference[]>([]);

  const isLeftActionButtonEnabled = scrollPosition > LEFT_SCROLL_OFFSET;

  const areActionButtonsEnabled = isLeftActionButtonEnabled || !scrollIsAtEnd;

  const [focusedCard, setFocusedCard] = useState(cards?.[0]?.id);

  const updateScrollButtonsState = () => {
    if (carouselElementRef.current) {
      const { scrollWidth, offsetWidth } = carouselElementRef.current;

      const scrollAtEnd = scrollWidth - offsetWidth <= scrollPosition + LEFT_SCROLL_OFFSET;
      setScrollIsAtEnd(scrollAtEnd);
    }

    const scrollDirecton = scrollPosition > previousScrollPosition ? 'right' : 'left';

    const cardsInFullViewIds: string[] = [];
    carouselCardsRef.current.forEach((card) => {
      if (isVisible(carouselElementRef.current as HTMLElement, card.cardElement as HTMLElement)) {
        // eslint-disable-next-line functional/immutable-data
        cardsInFullViewIds.push(card.cardElement.getAttribute('id') ?? '');
      }
    });

    if (cardsInFullViewIds.length >= 1) {
      const visibleCardIndex = scrollDirecton === 'right' ? cardsInFullViewIds.length - 1 : 0;
      const visibleCardId = cardsInFullViewIds[visibleCardIndex];
      setVisibleCardOnMobileView(visibleCardId);
      setFocusedCard(visibleCardId);
    }

    setPreviousScrollPosition(scrollPosition);
  };

  const scrollCarousel = (direction: 'left' | 'right' = 'right') => {
    if (carouselElementRef.current) {
      const { scrollWidth } = carouselElementRef.current;

      const cardWidth = scrollWidth / carouselCardsRef.current.length;

      const res = Math.floor(cardWidth - cardWidth * 0.05);

      carouselElementRef.current.scrollBy({
        left: direction === 'right' ? res : -res,
        behavior: 'smooth',
      });
    }
  };

  const handleOnKeyDown = (
    event: React.KeyboardEvent<HTMLAnchorElement | HTMLDivElement>,
    index: number,
  ) => {
    if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
      const nextIndex = event.key === 'ArrowRight' ? index + 1 : index - 1;
      const nextCard = cards[nextIndex];
      if (nextCard) {
        const ref = carouselCardsRef.current[nextIndex];
        if (ref.type === 'promo') {
          ref.anchorElement?.focus();
        } else {
          ref.cardElement?.focus();
        }

        scrollCardIntoView(carouselCardsRef.current[nextIndex].cardElement, nextCard);
        event.preventDefault();
      }
    }

    if (event.key === 'Enter' || event.key === ' ') {
      event.currentTarget.click();
    }
  };

  const scrollCardIntoView = (element: HTMLElement, card: CarouselCard) => {
    element.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center',
    });

    setFocusedCard(card.id);
  };

  useEffect(() => {
    updateScrollButtonsState();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [scrollPosition]);

  useEffect(() => {
    window.addEventListener('resize', updateScrollButtonsState);

    return () => {
      window.removeEventListener('resize', updateScrollButtonsState);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const addElementToCardsRefArray = (index: number, ref: Partial<CardsReference>) => {
    if (ref) {
      // eslint-disable-next-line functional/immutable-data
      carouselCardsRef.current[index] = {
        type: ref.type ?? carouselCardsRef.current?.[index]?.type,
        cardElement: ref.cardElement ?? carouselCardsRef.current?.[index]?.cardElement,
        anchorElement: ref.anchorElement ?? carouselCardsRef.current?.[index]?.anchorElement,
      };
    }
  };

  return (
    <div className={clsx('carousel-wrapper', className)}>
      <div className="d-flex justify-content-between carousel__header">
        {typeof header === 'string' ? (
          <Title as="span" type="title-body">
            {header}
          </Title>
        ) : (
          header
        )}
        {areActionButtonsEnabled ? (
          <div className="hidden-xs d-inline-flex">
            <IconButton
              size={32}
              tabIndex={-1}
              priority="tertiary"
              disabled={!isLeftActionButtonEnabled}
              aria-hidden="true"
              data-testid="scroll-carousel-left"
              onClick={() => scrollCarousel('left')}
            >
              <ChevronLeft />
            </IconButton>
            <IconButton
              size={32}
              tabIndex={-1}
              className="m-l-1"
              priority="tertiary"
              aria-hidden="true"
              data-testid="scroll-carousel-right"
              disabled={scrollIsAtEnd}
              onClick={() => scrollCarousel()}
            >
              <ChevronRight />
            </IconButton>
          </div>
        ) : null}
      </div>
      <div
        ref={carouselElementRef}
        tabIndex={-1}
        role="list"
        className="carousel"
        onScroll={(event) => {
          const target = event.target as HTMLElement;
          setScrollPosition(target.scrollLeft);
        }}
      >
        {cards?.map((card, index) => {
          const sharedProps = {
            id: card.id,
            className: clsx('carousel__card', {
              'carousel__card--focused': card.id === focusedCard,
            }),
            onClick: () => {
              card.onClick?.();
              onClick?.(card.id);
            },
            onFocus: (event: React.FocusEvent<HTMLAnchorElement | HTMLDivElement>) => {
              scrollCardIntoView(event.currentTarget, card);
            },
          };

          const cardContent =
            card.type !== 'promo' ? (
              <div
                id={`${card.id}-content`}
                className={clsx('carousel__card-content', {
                  [card.className ?? '']: !!card.className,
                })}
                style={card.styles}
              >
                {card.content}
              </div>
            ) : null;

          if (card.type === 'button') {
            return (
              <div key={card.id} aria-labelledby={`${card.id}-content`} role="listitem">
                <div
                  {...sharedProps}
                  ref={(el) => {
                    if (el) {
                      // eslint-disable-next-line functional/immutable-data
                      carouselCardsRef.current[index] = {
                        type: 'default',
                        cardElement: el,
                      };
                    }
                  }}
                  role="button"
                  tabIndex={0}
                  onKeyDown={(event) => handleOnKeyDown(event, index)}
                >
                  {cardContent}
                </div>
              </div>
            );
          }

          if (card.type === 'promo') {
            return (
              <div key={card.id} id={card.id} role="listitem" aria-labelledby={`${card.id}-anchor`}>
                <PromoCard
                  {...{ ...card, type: undefined }}
                  {...{ ...sharedProps }}
                  ref={(el: HTMLDivElement | null) => {
                    if (el) {
                      addElementToCardsRefArray(index, {
                        type: 'promo',
                        cardElement: el,
                      });
                    }
                  }}
                  anchorRef={(el: HTMLAnchorElement) => {
                    if (el) {
                      addElementToCardsRefArray(index, {
                        type: 'promo',
                        anchorElement: el,
                      });
                    }
                  }}
                  anchorId={`${card.id}-anchor`}
                  onKeyDown={(event) => handleOnKeyDown(event, index)}
                />
              </div>
            );
          }

          return (
            <div key={card.id} aria-labelledby={`${card.id}-content`} role="listitem">
              <a
                {...sharedProps}
                ref={(el) => {
                  if (el) {
                    // eslint-disable-next-line functional/immutable-data
                    carouselCardsRef.current[index] = {
                      type: 'default',
                      cardElement: el,
                    };
                  }
                }}
                href={card.href}
                target={card.hrefTarget}
                rel="noreferrer"
                onKeyDown={(event) => handleOnKeyDown(event, index)}
              >
                {cardContent}
              </a>
            </div>
          );
        })}
      </div>
      <div className="visible-xs">
        <div className="carousel__indicators">
          {cards?.map((card, index) => (
            <button
              key={`${card.id}-indicator`}
              data-testid={`${card.id}-indicator`}
              tabIndex={-1}
              aria-hidden
              type="button"
              className={clsx('carousel__indicator', {
                'carousel__indicator--selected': card.id === visibleCardOnMobileView,
              })}
              onClick={() => {
                scrollCardIntoView(carouselCardsRef.current[index].cardElement, card);
              }}
            />
          ))}
        </div>
      </div>
    </div>
  );
};

const isVisible = (container: HTMLElement, el: HTMLElement) => {
  const cWidth = container.offsetWidth;
  const cScrollOffset = container.scrollLeft;

  const elemLeft = el.offsetLeft - container.offsetLeft;
  const elemRight = elemLeft + el.offsetWidth;

  return elemLeft >= cScrollOffset && elemRight <= cScrollOffset + cWidth;
};

export default Carousel;
