import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { useDeepEffect } from '@devesharp/react-hooks';
import {
   closestCenter,
   DragOverlay,
   DndContext,
   KeyboardSensor,
   MouseSensor,
   TouchSensor,
   useSensor,
   useSensors,
} from '@dnd-kit/core';
import { restrictToWindowEdges } from '@dnd-kit/modifiers';
import {
   arrayMove,
   useSortable,
   SortableContext,
   sortableKeyboardCoordinates,
   rectSortingStrategy,
} from '@dnd-kit/sortable';
import { useImmer } from 'use-immer';
import * as S from './DNDContainer.style';

interface ISortableContainerProps<T = any> {
   id: string;
   index: number;
   useDragOverlay?: boolean;
   value: T;
   renderItem?(args: any): React.ReactElement;
}

export interface IDNDContainerProps {
   vertical?: boolean;
   horizontal?: boolean;
   items: { [key: string]: any; id: string | number }[];
   containerCardStyle: any;
   onChange: (newItems: any[], currentIndex: number, oldIndex: number, resource: any) => void;
   renderCard: (props: { data: any; dragging: boolean; dragOverlay: boolean; disabled: boolean; index: number }) => any;
}

export const DNDContainer = React.memo(function DNDContainer(props: IDNDContainerProps) {
   const { containerCardStyle = {}, horizontal = false, vertical = false } = props;
   const [items, setItems] = useImmer(props.items.map((el, i) => el?.id.toString() ?? i.toString()));

   useDeepEffect(() => {
      setItems(() => props.items.map((el, i) => el?.id.toString() ?? i.toString()));
   }, [props.items]);

   const [activeId, setActiveId] = useState<string | null>(null);

   const sensors = useSensors(
      useSensor(MouseSensor, {}),
      useSensor(TouchSensor, {}),
      useSensor(KeyboardSensor, {
         coordinateGetter: sortableKeyboardCoordinates,
      }),
   );

   const getIndex = items.indexOf.bind(items);
   const activeIndex = activeId ? getIndex(activeId) : -1;

   return (
      <DndContext
         sensors={sensors}
         collisionDetection={closestCenter}
         onDragStart={({ active }) => {
            if (!active) {
               return;
            }

            setActiveId(active.id);
         }}
         onDragEnd={({ over }) => {
            setActiveId(null);

            if (over) {
               const overIndex = getIndex(over.id);
               if (activeIndex !== overIndex) {
                  props.onChange(
                     arrayMove(props.items, activeIndex, overIndex),
                     overIndex,
                     activeIndex,
                     props.items[activeIndex],
                  );
               }
            }
         }}
         onDragCancel={() => setActiveId(null)}
         modifiers={[restrictToWindowEdges]}
      >
         <S.Wrapper horizontal={horizontal} vertical={vertical}>
            <SortableContext items={items} strategy={rectSortingStrategy}>
               {items.map((value, index) => {
                  if (!props.items[Number(index)]) return null;

                  return (
                     <SortableItem
                        key={props.items[Number(index)]?.id}
                        id={value}
                        value={props.items[Number(index)]}
                        index={index}
                        useDragOverlay
                        renderItem={({ value: data, listeners, dragging, dragOverlay, disabled, index: i }) => (
                           <div style={{ ...containerCardStyle, flex: 1 }} className="dnd-item">
                              <div {...listeners} className="dnd-item--inner">
                                 {props.renderCard({ data, dragOverlay, dragging, disabled, index: i })}
                              </div>
                           </div>
                        )}
                     />
                  );
               })}
            </SortableContext>
         </S.Wrapper>

         {createPortal(
            <DragOverlay adjustScale>
               {activeId ? (
                  <Item
                     value={props.items[activeIndex]}
                     handle={false}
                     dragOverlay
                     renderItem={({ value: data, listeners, dragging, disabled, dragOverlay, index }) => (
                        <div style={{ ...containerCardStyle, flex: 1 }} className="dnd-item">
                           {props.renderCard({ data, dragOverlay, dragging, disabled, index })}
                        </div>
                     )}
                  />
               ) : null}
            </DragOverlay>,
            document.body,
         )}
      </DndContext>
   );
});

function SortableItem({ id, index, useDragOverlay, value, renderItem }: ISortableContainerProps): any {
   const { isDragging, isSorting, listeners, setNodeRef, transform, transition } = useSortable({
      id,
      disabled: !!value?.disabled,
   });

   return (
      <Item
         ref={setNodeRef}
         value={value}
         dragging={isDragging}
         sorting={isSorting}
         index={index}
         transform={transform}
         transition={!useDragOverlay && isDragging ? 'none' : transition}
         listeners={listeners}
         dragOverlay={!useDragOverlay && isDragging}
         renderItem={renderItem}
         disabled={!!value?.disabled}
      />
   );
}

// eslint-disable-next-line react/display-name
const Item = React.memo(
   React.forwardRef<HTMLDivElement, any>(function Item(
      {
         color,
         dragOverlay,
         dragging,
         handle,
         height,
         index,
         listeners,
         onRemove,
         sorting,
         transition,
         transform,
         value,
         wrapperStyle,
         disabled,
         renderItem,
         ...props
      },
      ref,
   ) {
      useEffect(() => {
         if (!dragOverlay) {
            return;
         }

         document.body.style.cursor = 'grabbing';

         return () => {
            document.body.style.cursor = '';
         };
      }, [dragOverlay]);

      return (
         <S.ItemContainer
            style={
               {
                  // transition: dragging ? 'transform 200ms ease' : undefined,
                  '--translate-x': transform ? `${Math.round(transform.x)}px` : undefined,
                  '--translate-y': transform ? `${Math.round(transform.y)}px` : undefined,
                  '--scale-x': transform?.scaleX ? `${transform.scaleX}` : undefined,
                  '--scale-y': transform?.scaleY ? `${transform.scaleY}` : undefined,
                  '--index': index,
                  // '--dragging': dragging ? 'true' : 'false',
                  '--color': color,
               } as React.CSSProperties
            }
            ref={ref}
         >
            {renderItem({ value, dragging, dragOverlay, disabled, listeners, index })}
         </S.ItemContainer>
      );
   }),
);
