import { useCallback, useRef } from 'react';

import type { HorizontalScrollerProps } from '~/src/components/Scroller2/HorizontalScroller';
import type { SortableContainerProps } from '~/src/components/Sortable';
import type { CSSProperties, FC, ReactNode } from 'react';

import HorizontalScroller, {
  SCROLLER_ARROW_CLASS,
} from '~/src/components/Scroller2/HorizontalScroller';
import { SortableContainer } from '~/src/components/Sortable';
import useIsLargeScreen from '~/src/hooks/useIsLargeScreen';

const SCROLLER_CLASS = 'sortableScroller';
const NOOP = () => {};

interface SortableHorizontalScrollerProps
  extends Pick<SortableContainerProps, 'onDrop' | 'onDragStart'>,
    Omit<HorizontalScrollerProps, 'renderContent'> {
  centerContent?: boolean;
  withInitialDragScrollPositionCorrection?: boolean;

  renderContent: (params: {
    itemStyle: CSSProperties;
    itemClassName: string;
  }) => ReactNode;

  onBeforeDragStart?: (
    params: Parameters<
      Exclude<SortableContainerProps['onBeforeDragStart'], undefined>
    >[0] & { scrollerEl: HTMLElement }
  ) => void;

  onDragEnd?: (
    params: Parameters<
      Exclude<SortableContainerProps['onDragEnd'], undefined>
    >[0] & { scrollerEl: HTMLElement }
  ) => void;
}

const SortableHorizontalScroller: FC<SortableHorizontalScrollerProps> = ({
  onDrop,
  renderContent,
  centerContent,
  onBeforeDragStart = NOOP,
  onDragStart = NOOP,
  onDragEnd = NOOP,
  contentStyle,
  withInitialDragScrollPositionCorrection = true,
  ...scrollerProps
}) => {
  const scrollerRef = useRef<HTMLDivElement>(null);
  const isLargeScreen = useIsLargeScreen();
  const scrollerFinalRef = scrollerProps.scrollerRef ?? scrollerRef;

  return (
    <HorizontalScroller
      {...scrollerProps}
      className={SCROLLER_CLASS}
      scrollerRef={scrollerFinalRef}
      overflowStyle="inside"
      contentStyle={{
        display: 'block',
      }}
      // WARN: snapping breaks dnd scrolling
      // withSnapping={false}
      render={useCallback(
        ({ ref }) => {
          return (
            <SortableContainer
              nodeRef={ref}
              axis="x"
              tag="ul"
              style={{
                minWidth: '100%',
                display: 'inline-flex',
                justifyContent: centerContent ? 'center' : '',
                alignItems: 'center',
                ...contentStyle,
              }}
              onBeforeDragStart={(params) => {
                const { containerEl, dragItemEl } = params;

                const scrollContainerEl = containerEl.closest(
                  `.${SCROLLER_CLASS}`
                );

                if (isLargeScreen) {
                  const arrowEls = scrollContainerEl?.querySelectorAll(
                    `.${SCROLLER_ARROW_CLASS}`
                  );

                  // hide the horizontal scroller arrows when dragging
                  // to avoid conflicting with dnd scroll bound detection

                  [].forEach.call(arrowEls, (el: HTMLDivElement) => {
                    el.style.display = 'none';
                  });
                }

                const scrollerEl = scrollerFinalRef.current;
                if (!scrollerEl) return;

                if (withInitialDragScrollPositionCorrection) {
                  // when drag item is partially scrolled out of view scroll it into view
                  // to avoid smooth-dnd flash rendering it inside container bounds.
                  const underflowX =
                    dragItemEl.offsetLeft - scrollerEl.scrollLeft;

                  if (underflowX < 0) {
                    scrollerEl.scrollLeft += underflowX;
                  } else {
                    const overflowX =
                      dragItemEl.offsetLeft +
                      dragItemEl.clientWidth -
                      (scrollerEl.scrollLeft + scrollerEl.clientWidth);

                    if (overflowX > 0) {
                      scrollerEl.scrollLeft += overflowX;
                    }
                  }
                }

                onBeforeDragStart({
                  ...params,
                  scrollerEl: scrollerFinalRef.current!,
                });
              }}
              onDragEnd={(params) => {
                const { containerEl: dragContainerEl } = params;

                const scrollerEl = dragContainerEl.closest(
                  `.${SCROLLER_CLASS}`
                );

                if (isLargeScreen) {
                  const els =
                    scrollerEl &&
                    Array.from(
                      scrollerEl.querySelectorAll(`.${SCROLLER_ARROW_CLASS}`)
                    );

                  els?.forEach((el: HTMLDivElement) => {
                    el.style.display = '';
                  });
                }

                onDragEnd({
                  ...params,
                  scrollerEl: scrollerFinalRef.current!,
                });
              }}
              onDragStart={onDragStart}
              onDrop={({ removedIndex, addedIndex }) => {
                const didChange = removedIndex !== addedIndex;

                if (!didChange) {
                  return;
                }

                if (onDrop) {
                  onDrop({
                    removedIndex,
                    addedIndex,
                  });
                }
              }}
            >
              {renderContent({
                itemClassName: 'horizontalSortableItem',
                itemStyle: {
                  // override default 'table-cell'
                  display: 'block',
                  flexShrink: 0,
                },
              })}
              {/* override clickable hover style when dragging */}
              <style jsx global>{`
                .smooth-dnd-ghost.horizontalSortableItem > * {
                  opacity: 1 !important;
                }
              `}</style>
            </SortableContainer>
          );
        },
        [renderContent, onDrop, isLargeScreen]
      )}
    />
  );
};

export default SortableHorizontalScroller;
