"use client";

import { useState, useRef, useCallback, useEffect } from "react";
import { createPortal } from "react-dom";
import { Plus, Trash2, GripVertical } from "lucide-react";

interface ArrayEditorProps<T> {
  items: T[];
  onChange: (items: T[]) => void;
  renderItem: (item: T, index: number, update: (item: T) => void) => React.ReactNode;
  createItem: () => T;
  label?: string;
  addLabel?: string;
}

export function ArrayEditor<T>({
  items,
  onChange,
  renderItem,
  createItem,
  label,
  addLabel = "Добавить",
}: ArrayEditorProps<T>) {
  const [dragIndex, setDragIndex] = useState<number | null>(null);
  const [insertAt, setInsertAt] = useState<number | null>(null);
  const [mousePos, setMousePos] = useState({ x: 0, y: 0 });
  const [dragSize, setDragSize] = useState({ w: 0, h: 0 });
  const [grabOffset, setGrabOffset] = useState({ x: 0, y: 0 });
  const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
  const [mounted, setMounted] = useState(false);

  useEffect(() => { setMounted(true); }, []);

  function updateItem(index: number, item: T) {
    const updated = [...items];
    updated[index] = item;
    onChange(updated);
  }

  function removeItem(index: number) {
    onChange(items.filter((_, i) => i !== index));
  }

  const startDrag = useCallback(
    (clientX: number, clientY: number, index: number) => {
      const el = itemRefs.current[index];
      if (!el) return;
      const rect = el.getBoundingClientRect();
      setDragIndex(index);
      setInsertAt(index);
      setMousePos({ x: clientX, y: clientY });
      setDragSize({ w: rect.width, h: rect.height });
      setGrabOffset({ x: clientX - rect.left, y: clientY - rect.top });
    },
    []
  );

  const handleGripMouseDown = useCallback(
    (e: React.MouseEvent, index: number) => {
      e.preventDefault();
      startDrag(e.clientX, e.clientY, index);
    },
    [startDrag]
  );

  useEffect(() => {
    if (dragIndex === null) return;

    document.body.style.userSelect = "none";

    function onMouseMove(e: MouseEvent) {
      setMousePos({ x: e.clientX, y: e.clientY });

      let newInsert = items.length;
      for (let i = 0; i < items.length; i++) {
        if (i === dragIndex) continue;
        const el = itemRefs.current[i];
        if (!el) continue;
        const rect = el.getBoundingClientRect();
        const midY = rect.top + rect.height / 2;
        if (e.clientY < midY) {
          newInsert = i;
          break;
        }
      }
      setInsertAt(newInsert);
    }

    function onMouseUp() {
      // Read current values from state updaters but defer onChange to avoid
      // calling parent setState during React's render/updater cycle
      let capturedDrag: number | null = null;
      let capturedInsert: number | null = null;

      setDragIndex((prev) => { capturedDrag = prev; return null; });
      setInsertAt((prev) => { capturedInsert = prev; return null; });

      // Defer the reorder to next microtask so React finishes its batch first
      queueMicrotask(() => {
        if (capturedDrag !== null && capturedInsert !== null) {
          let targetIndex = capturedInsert;
          if (capturedDrag < targetIndex) targetIndex -= 1;
          if (capturedDrag !== targetIndex) {
            const updated = [...items];
            const [moved] = updated.splice(capturedDrag, 1);
            updated.splice(targetIndex, 0, moved);
            onChange(updated);
          }
        }
      });
    }

    window.addEventListener("mousemove", onMouseMove);
    window.addEventListener("mouseup", onMouseUp);
    return () => {
      document.body.style.userSelect = "";
      window.removeEventListener("mousemove", onMouseMove);
      window.removeEventListener("mouseup", onMouseUp);
    };
  }, [dragIndex, items, onChange]);

  function renderList() {
    if (dragIndex === null || insertAt === null) {
      return items.map((item, i) => (
        <div
          key={i}
          ref={(el) => { itemRefs.current[i] = el; }}
          className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-colors"
        >
          <div className="flex items-start justify-between gap-2 mb-3">
            <div
              className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-white transition-colors select-none"
              onMouseDown={(e) => handleGripMouseDown(e, i)}
            >
              <GripVertical size={16} />
            </div>
            <button
              type="button"
              onClick={() => removeItem(i)}
              className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors"
            >
              <Trash2 size={16} />
            </button>
          </div>
          {renderItem(item, i, (updated) => updateItem(i, updated))}
        </div>
      ));
    }

    const elements: React.ReactNode[] = [];
    let visualIndex = 0;
    let placeholderPos = insertAt;
    if (insertAt > dragIndex) placeholderPos = insertAt - 1;

    for (let i = 0; i < items.length; i++) {
      if (i === dragIndex) {
        elements.push(
          <div key={`hidden-${i}`} ref={(el) => { itemRefs.current[i] = el; }} className="hidden" />
        );
        continue;
      }

      if (visualIndex === placeholderPos) {
        elements.push(
          <div
            key="placeholder"
            className="rounded-lg border-2 border-dashed border-rose-500/50 bg-rose-500/5 mb-3"
            style={{ height: dragSize.h }}
          />
        );
      }

      const item = items[i];
      elements.push(
        <div
          key={i}
          ref={(el) => { itemRefs.current[i] = el; }}
          className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-colors"
        >
          <div className="flex items-start justify-between gap-2 mb-3">
            <div
              className="cursor-grab active:cursor-grabbing rounded p-1 text-neutral-500 hover:text-white transition-colors select-none"
              onMouseDown={(e) => handleGripMouseDown(e, i)}
            >
              <GripVertical size={16} />
            </div>
            <button
              type="button"
              onClick={() => removeItem(i)}
              className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors"
            >
              <Trash2 size={16} />
            </button>
          </div>
          {renderItem(item, i, (updated) => updateItem(i, updated))}
        </div>
      );
      visualIndex++;
    }

    if (visualIndex === placeholderPos) {
      elements.push(
        <div
          key="placeholder"
          className="rounded-lg border-2 border-dashed border-rose-500/50 bg-rose-500/5 mb-3"
          style={{ height: dragSize.h }}
        />
      );
    }

    return elements;
  }

  return (
    <div>
      {label && (
        <h3 className="text-sm font-medium text-neutral-300 mb-3">{label}</h3>
      )}

      <div>
        {renderList()}
      </div>

      <button
        type="button"
        onClick={() => onChange([...items, createItem()])}
        className="mt-3 flex items-center gap-2 rounded-lg border border-dashed border-white/20 px-4 py-2.5 text-sm text-neutral-400 hover:text-white hover:border-white/40 transition-colors"
      >
        <Plus size={16} />
        {addLabel}
      </button>

      {/* Floating clone following cursor */}
      {mounted && dragIndex !== null &&
        createPortal(
          <div
            className="fixed z-[9999] pointer-events-none"
            style={{
              left: mousePos.x - grabOffset.x,
              top: mousePos.y - grabOffset.y,
              width: dragSize.w,
              height: dragSize.h,
            }}
          >
            <div className="h-full rounded-lg border-2 border-rose-500 bg-neutral-900/95 shadow-2xl shadow-rose-500/20 flex items-center gap-3 px-4">
              <GripVertical size={16} className="text-rose-400 shrink-0" />
              <span className="text-sm text-neutral-300">Перемещение элемента...</span>
            </div>
          </div>,
          document.body
        )}
    </div>
  );
}
