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

import type { ClickableOnClick } from '~/src/components/Clickable';
import type { FC } from 'react';
import type { CarouselItem } from '../types';

import Box from '~/src/components/Box';
import { SortableItem } from '~/src/components/Sortable';
import Text from '~/src/components/Text';
import { usePageTheme } from '../../../hooks/theme';
import SortableHorizontalScroller from '../../lib/SortableHorizontalScroller';
import CarouselSectionItem from '../lib/CarouselSectionItem';

const debug = Debug('songwhip/CarouselSection');

const SortableCarouselScroller: FC<{
  items: CarouselItem[];
  onItemClick: ClickableOnClick<number>;
  onChange: (params: { items: CarouselItem[] }) => void;
}> = ({ items, onItemClick, onChange }) => {
  const dragInfoRef = useRef<any>(null);
  const pageTheme = usePageTheme();

  const isEmpty = !items.length;
  const VISUAL_ITEM_WIDTH = 0.9;

  const contentWidth = !isEmpty
    ? `${items.length * VISUAL_ITEM_WIDTH * 100}%`
    : undefined;

  const itemWidthRelativeToContainer = 100 / items.length;
  const itemWidth = `${itemWidthRelativeToContainer}%`;

  return (
    <>
      <SortableHorizontalScroller
        margin="0 -1"
        withInitialDragScrollPositionCorrection={false}
        gradientColor={pageTheme.backgroundColor}
        contentStyle={{
          display: 'flex',
          width: contentWidth,
        }}
        renderContent={useCallback(
          ({ itemStyle, itemClassName }) => {
            if (isEmpty) {
              return (
                <Box padding="0 0 85%" positionRelative fullWidth>
                  <Text
                    isCentered
                    size="1.6rem"
                    color="#555"
                    padding="2rem 0"
                    coverParent
                    centerContent
                  >
                    Section empty
                  </Text>
                </Box>
              );
            }

            return items.map(({ link, text, image }, index) => {
              return (
                <SortableItem
                  tag="li"
                  key={link}
                  className={`${itemClassName} carouselItem`}
                  style={{
                    ...itemStyle,
                    padding: '0 1px',
                    width: itemWidth,
                    height: '100%',
                    flexShrink: 0,
                  }}
                >
                  <CarouselSectionItem
                    text={text}
                    onClick={onItemClick}
                    data={index}
                    withActiveStyle={false}
                    image={image}
                    fullHeight
                  />
                </SortableItem>
              );
            });
          },
          [items]
        )}
        onBeforeDragStart={useCallback(
          ({ dragItemEl, itemEls, pointerX, scrollerEl, containerEl }) => {
            debug('on before drag start');

            const { left: dragItemX, width: originalItemWidth } =
              dragItemEl.getBoundingClientRect();

            const containerHeight = containerEl.getBoundingClientRect().height;

            // scale down items being dragged
            const newItemWidth = originalItemWidth * 0.5;

            const itemPointerX = pointerX - dragItemX;
            const pointerOnRightHalf = itemPointerX > originalItemWidth / 2;
            const dragItemIndex = itemEls.indexOf(dragItemEl);
            const scrollViewportWidth = scrollerEl.clientWidth;
            const newContentWidth = newItemWidth * itemEls.length;
            const originalScrollLeft = scrollerEl.scrollLeft;
            const itemOffsetLeft = dragItemEl.offsetLeft;
            const itemViewportX = itemOffsetLeft - originalScrollLeft;

            const newMaxScrollLeft = Math.max(
              newContentWidth - scrollViewportWidth,
              0
            );

            let newItemViewportX = itemViewportX;

            // When the user grabs the item on the right hand side we subtly
            // change the behavior so that the right edge appears to lead the dragging.
            if (pointerOnRightHalf) {
              newItemViewportX += newItemWidth;
            }

            const maxItemViewportX = scrollViewportWidth - newItemWidth;
            newItemViewportX = Math.min(newItemViewportX, maxItemViewportX);

            const newItemOffsetLeft = dragItemIndex * newItemWidth;
            const newScrollLeft = newItemOffsetLeft - newItemViewportX;

            containerEl.style.height = `${containerHeight}px`;
            containerEl.style.width = `${newContentWidth}px`;
            containerEl.style.boxSizing = 'content-box';

            // scale the items down
            itemEls.forEach((el) => {
              el.style.width = `${newItemWidth}px`;
            });

            // COMPLEX: When there's not enough scroll before/after to position
            // the scroll contents when we need them, then we need to add padding
            // before or after the content to allow the scroller to scroll more.
            if (newScrollLeft < 0) {
              containerEl.style.paddingLeft = `${Math.abs(newScrollLeft)}px`;
            } else if (newScrollLeft > newMaxScrollLeft) {
              containerEl.style.paddingRight = `${
                newScrollLeft - newMaxScrollLeft
              }px`;
            }

            scrollerEl.scrollLeft = newScrollLeft;

            dragInfoRef.current = {
              pointerOnRightHalf,
              originalItemWidth,
              newItemWidth,
            };
          },
          []
        )}
        onDragEnd={useCallback(
          ({ dragItemEl, itemEls, scrollerEl, containerEl }) => {
            debug('on drag end');

            const { pointerOnRightHalf, newItemWidth } = dragInfoRef.current;

            const scrollLeft = scrollerEl.scrollLeft;
            let dragItemViewportX = dragItemEl.offsetLeft - scrollLeft;

            if (pointerOnRightHalf) {
              dragItemViewportX -= newItemWidth;
            }

            containerEl.style.paddingLeft = containerEl.style.paddingRight = '';

            containerEl.style.width = contentWidth;
            containerEl.style.height = '';

            itemEls.forEach((el) => {
              debug('set width back', itemWidth);
              el.style.width = itemWidth;
            });

            const newScrollLeft = dragItemEl.offsetLeft - dragItemViewportX;

            scrollerEl.scrollLeft = newScrollLeft;
          },
          [itemWidth]
        )}
        onDrop={useCallback(
          ({ removedIndex, addedIndex }) => {
            debug('on drop');

            const itemsNext = [...items];
            const [removed] = itemsNext.splice(removedIndex, 1);

            itemsNext.splice(addedIndex, 0, removed);
            debug('order change', itemsNext);

            onChange({
              items: itemsNext,
            });
          },
          [items, onChange]
        )}
      />
      <style jsx>{`
        :global(.carouselItem.smooth-dnd-ghost) {
          opacity: 0.8;
          box-shadow: 0 0.1rem 2rem #000;
          border-radius: 2.8rem;
          overflow: hidden !important;
          padding: 0 !important;
        }

        :global(.carouselItem.smooth-dnd-ghost .text) {
          display: none;
        }
      `}</style>
    </>
  );
};

export default SortableCarouselScroller;
