import type { ReactNode } from "react";
import {
  closestCenter,
  DndContext,
  PointerSensor,
  useDraggable,
  useDroppable,
  useSensor,
  useSensors,
  type DragEndEvent,
} from "@dnd-kit/core";
import {
  SortableContext,
  arrayMove,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { cn } from "./cn.js";

export type NodedcDragData = Record<string, unknown>;

export interface DragRect {
  left: number;
  right: number;
  top: number;
  bottom: number;
  width: number;
  height: number;
}

export interface DragDropResult {
  activeId: string;
  overId: string | null;
  activeData?: NodedcDragData;
  overData?: NodedcDragData;
  activeRect?: DragRect;
  overRect?: DragRect;
}

const copyRect = (rect: DragRect | null | undefined): DragRect | undefined => rect ? ({
  left: rect.left,
  right: rect.right,
  top: rect.top,
  bottom: rect.bottom,
  width: rect.width,
  height: rect.height,
}) : undefined;

export function DragHandle({ label = "Перетащить", listeners, attributes, className }: {
  label?: string;
  listeners?: ReturnType<typeof useDraggable>["listeners"];
  attributes?: ReturnType<typeof useDraggable>["attributes"];
  className?: string;
}) {
  return (
    <button
      type="button"
      className={cn("nodedc-drag-handle", className)}
      aria-label={label}
      {...attributes}
      {...listeners}
      onClick={(event) => event.stopPropagation()}
    >
      <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
        <circle cx="4" cy="4" r="0.75" /><circle cx="8" cy="4" r="0.75" /><circle cx="12" cy="4" r="0.75" />
        <circle cx="4" cy="8" r="0.75" /><circle cx="8" cy="8" r="0.75" /><circle cx="12" cy="8" r="0.75" />
      </svg>
    </button>
  );
}

export function DragDropRoot({ children, onDragEnd }: { children: ReactNode; onDragEnd: (result: DragDropResult) => void }) {
  const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
  const handleDragEnd = (event: DragEndEvent) => {
    onDragEnd({
      activeId: String(event.active.id),
      overId: event.over ? String(event.over.id) : null,
      activeData: event.active.data.current as NodedcDragData | undefined,
      overData: event.over?.data.current as NodedcDragData | undefined,
      activeRect: copyRect(event.active.rect.current.translated),
      overRect: copyRect(event.over?.rect),
    });
  };
  return <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>{children}</DndContext>;
}

export function DraggableItem({ id, data, className, children }: {
  id: string;
  data?: NodedcDragData;
  className?: string;
  children: (state: { handle: ReactNode; isDragging: boolean }) => ReactNode;
}) {
  const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ id, data });
  return (
    <div
      ref={setNodeRef}
      className={cn(className, isDragging && "is-dragging")}
      style={{ transform: CSS.Translate.toString(transform), opacity: isDragging ? 0.65 : undefined }}
    >
      {children({ handle: <DragHandle listeners={listeners} attributes={attributes} />, isDragging })}
    </div>
  );
}

export function DropZone({ id, data, className, children }: { id: string; data?: NodedcDragData; className?: string; children: ReactNode }) {
  const { isOver, setNodeRef } = useDroppable({ id, data });
  return <div ref={setNodeRef} className={cn(className, isOver && "is-drag-over")}>{children}</div>;
}

export function SortableScope({ ids, children }: { ids: string[]; children: ReactNode }) {
  return <SortableContext items={ids} strategy={verticalListSortingStrategy}>{children}</SortableContext>;
}

export function SortableItem({ id, data, className, children }: {
  id: string;
  data?: NodedcDragData;
  className?: string;
  children: (state: { handle: ReactNode; isDragging: boolean }) => ReactNode;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, data });
  const verticalTransform = transform ? { ...transform, x: 0 } : null;
  return (
    <div
      ref={setNodeRef}
      className={cn(className, isDragging && "is-dragging")}
      style={{ transform: CSS.Transform.toString(verticalTransform), transition, opacity: isDragging ? 0.65 : undefined }}
    >
      {children({ handle: <DragHandle listeners={listeners} attributes={attributes} />, isDragging })}
    </div>
  );
}

export function SortableList<T>({ items, getId, className, onReorder, children }: {
  items: T[];
  getId: (item: T) => string;
  className?: string;
  onReorder: (items: T[]) => void;
  children: (item: T, state: { handle: ReactNode; isDragging: boolean }) => ReactNode;
}) {
  const ids = items.map(getId);
  return (
    <DragDropRoot onDragEnd={({ activeId, overId }) => {
      if (!overId || activeId === overId) return;
      const from = ids.indexOf(activeId);
      const to = ids.indexOf(overId);
      if (from >= 0 && to >= 0) onReorder(arrayMove(items, from, to));
    }}>
      <SortableScope ids={ids}>
        <div className={className}>
          {items.map((item) => (
            <SortableItem key={getId(item)} id={getId(item)}>
              {(state) => children(item, state)}
            </SortableItem>
          ))}
        </div>
      </SortableScope>
    </DragDropRoot>
  );
}
