import { useState } from 'react';

import {
  closestCenter,
  DndContext,
  type DragEndEvent,
  DragOverlay,
  type DropAnimation,
  defaultDropAnimationSideEffects,
  KeyboardSensor,
  type Modifiers,
  PointerSensor,
  type UniqueIdentifier,
  useSensor,
  useSensors,
} from '@dnd-kit/core';
import {
  restrictToFirstScrollableAncestor,
  restrictToHorizontalAxis,
  restrictToVerticalAxis,
} from '@dnd-kit/modifiers';
import {
  arrayMove,
  horizontalListSortingStrategy,
  SortableContext,
  sortableKeyboardCoordinates,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { Portal, Stack } from '@mui/material';

import SortableItem from './components/SortableItem';
import { type BaseSortableItem, type SortableListProps } from './types';

const customDropAnimationConfig: DropAnimation = {
  sideEffects: defaultDropAnimationSideEffects({
    styles: {
      active: {
        opacity: '0.4',
      },
    },
  }),
};

export const SortableList = <T extends BaseSortableItem>({
  onSort,
  items,
  ItemComponent,
  direction = 'vertical',
  sx = {},
  dragByHandler = false,
  hasDragOverlay = false,
  restrictToAncestor = false,
  disabled = false,
  slotProps,
}: SortableListProps<T>) => {
  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: {
        distance: 8, // for buttons inside draggable items
      },
    }),
    useSensor(KeyboardSensor, {
      coordinateGetter: sortableKeyboardCoordinates,
    }),
  );

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;

    if (over != null && active.id !== over.id) {
      const oldIndex = items.findIndex(item => item.id === active.id);
      const newIndex = items.findIndex(item => item.id === over.id);

      if (oldIndex === -1 || newIndex === -1) return;

      onSort(arrayMove(items, oldIndex, newIndex), oldIndex, newIndex);
    }

    setActiveId(null);
  };

  const modifiers: Modifiers = [
    direction === 'vertical'
      ? restrictToVerticalAxis
      : restrictToHorizontalAxis,
    ...(restrictToAncestor ? [restrictToFirstScrollableAncestor] : []),
  ];

  return (
    <DndContext
      onDragStart={({ active }) => setActiveId(active.id)}
      onDragEnd={handleDragEnd}
      onDragCancel={() => setActiveId(null)}
      sensors={sensors}
      collisionDetection={closestCenter}
      modifiers={modifiers}
    >
      <SortableContext
        items={items.map(i => i.id)}
        disabled={disabled}
        strategy={
          direction === 'vertical'
            ? verticalListSortingStrategy
            : horizontalListSortingStrategy
        }
      >
        <Stack
          component="ul"
          sx={{
            listStyle: 'none',
            listStyleType: 'none',
            appearance: 'none',
            padding: 0,
            margin: 0,
            ...(direction === 'vertical'
              ? { flexDirection: 'column', overflowY: 'auto' }
              : { flexDirection: 'row', overflowX: 'auto' }),
            ...sx,
          }}
        >
          {items.map((item, index) => (
            <SortableItem
              dragByHandler={dragByHandler}
              ItemComponent={ItemComponent}
              key={item.id}
              item={item}
              index={index}
              slotProps={slotProps}
            />
          ))}
        </Stack>
      </SortableContext>
      {hasDragOverlay && (
        <Portal>
          <DragOverlay
            dropAnimation={
              hasDragOverlay ? customDropAnimationConfig : undefined
            }
          >
            {activeId && (
              <SortableItem
                dragByHandler={dragByHandler}
                ItemComponent={ItemComponent}
                item={items.find(item => item.id === activeId) as T}
                hasDragOverlay={hasDragOverlay}
                slotProps={slotProps}
                index={-1}
              />
            )}
          </DragOverlay>
        </Portal>
      )}
    </DndContext>
  );
};

export default SortableList;
