import { type FC } from 'react';

import {
  closestCenter,
  DndContext,
  type DragEndEvent,
  KeyboardSensor,
  PointerSensor,
  type UniqueIdentifier,
  useSensor,
  useSensors,
} from '@dnd-kit/core';
import { restrictToHorizontalAxis } from '@dnd-kit/modifiers';
import {
  arrayMove,
  horizontalListSortingStrategy,
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Stack } from '@mui/material';

type SortableItem = { id: UniqueIdentifier };
type ItemComponentProps<T extends SortableItem> = { item: T };
type SortableItemProps<T extends SortableItem> = {
  item: T;
  ItemComponent: FC<ItemComponentProps<T>>;
};
const SortableItem = <T extends SortableItem>({
  item,
  ItemComponent,
}: SortableItemProps<T>) => {
  const { attributes, listeners, setNodeRef, transform, transition } =
    useSortable({ id: item.id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
  };

  return (
    <div
      ref={setNodeRef}
      style={style}
      {...attributes}
      {...listeners}
    >
      <ItemComponent item={item} />
    </div>
  );
};

export type SortableProps<T extends SortableItem> = {
  items: T[];
  onSort: (sortedItems: T[]) => void;
  ItemComponent: FC<{ item: T }>;
};

export const Sortable = <T extends SortableItem>({
  onSort,
  items,
  ItemComponent,
}: SortableProps<T>) => {
  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 (active.id !== over!.id) {
      const oldIndex = items.findIndex(f => f.id === active.id);
      const newIndex = items.findIndex(f => f.id === over?.id);

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

  return (
    <DndContext
      onDragEnd={handleDragEnd}
      sensors={sensors}
      collisionDetection={closestCenter}
      modifiers={[restrictToHorizontalAxis]}
    >
      <SortableContext
        items={items}
        strategy={horizontalListSortingStrategy}
      >
        <Stack sx={{ flexDirection: 'row', gap: 2, overflowX: 'auto' }}>
          {items.map(item => (
            <SortableItem
              ItemComponent={ItemComponent}
              key={item.id}
              item={item}
            />
          ))}
        </Stack>
      </SortableContext>
    </DndContext>
  );
};

export default Sortable;
