"use client";

import { useState, useRef, useCallback, useEffect } from "react";
import { createPortal } from "react-dom";
import { Plus, Trash2, GripVertical, ChevronDown, ChevronsUpDown } from "lucide-react";
import { ConfirmDialog } from "./ConfirmDialog";

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;
  collapsible?: boolean;
  getItemTitle?: (item: T, index: number) => string;
  getItemBadge?: (item: T, index: number) => React.ReactNode;
  hiddenItems?: Set<number>;
  addPosition?: "top" | "bottom";
}

export function ArrayEditor<T>({
  items,
  onChange,
  renderItem,
  createItem,
  label,
  addLabel = "Добавить",
  collapsible = false,
  getItemTitle,
  getItemBadge,
  hiddenItems,
  addPosition = "bottom",
}: ArrayEditorProps<T>) {
  const [confirmDelete, setConfirmDelete] = useState<number | null>(null);
  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);
  const [newItemIndex, setNewItemIndex] = useState<number | null>(null);
  const [droppedIndex, setDroppedIndex] = useState<number | null>(null);
  const [collapsed, setCollapsed] = useState<Set<number>>(() => collapsible ? new Set(items.map((_, i) => i)) : new Set());

  function toggleCollapse(index: number) {
    setCollapsed(prev => {
      const next = new Set(prev);
      if (next.has(index)) next.delete(index);
      else next.add(index);
      return next;
    });
  }

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

  // Scroll to newly added item
  useEffect(() => {
    if (newItemIndex !== null && itemRefs.current[newItemIndex]) {
      itemRefs.current[newItemIndex]?.scrollIntoView({ behavior: "smooth", block: "center" });
      setNewItemIndex(null);
    }
  }, [newItemIndex, items]);

  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);
            setDroppedIndex(targetIndex);
            setTimeout(() => setDroppedIndex(null), 1500);
          }
        }
      });
    }

    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) => {
        const isCollapsed = collapsible && collapsed.has(i) && newItemIndex !== i;
        const isHidden = hiddenItems?.has(i) ?? false;
        const title = getItemTitle?.(item, i) || `#${i + 1}`;
        return (
          <div
            key={i}
            ref={(el) => { itemRefs.current[i] = el; }}
            className={`rounded-lg border bg-neutral-900/50 mb-3 hover:border-white/25 hover:bg-neutral-800/50 focus-within:border-gold/50 focus-within:bg-neutral-800 transition-all ${
              newItemIndex === i || droppedIndex === i ? "border-gold/40 ring-1 ring-gold/20" : "border-white/10"
            } ${isHidden ? "hidden" : ""}`}
          >
            <div className={`flex items-center justify-between gap-2 p-4 ${isCollapsed ? "" : "pb-0 mb-3"}`}>
              <div className="flex items-center gap-2 flex-1 min-w-0">
                <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)}
                  aria-label="Перетащить для сортировки"
                  role="button"
                >
                  <GripVertical size={16} />
                </div>
                {collapsible && (
                  <button
                    type="button"
                    onClick={() => toggleCollapse(i)}
                    className="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer group"
                  >
                    <span className="text-sm font-medium text-neutral-300 truncate group-hover:text-white transition-colors">{title}</span>
                    {getItemBadge?.(item, i)}
                    <ChevronDown size={14} className={`text-neutral-500 transition-transform duration-200 shrink-0 ${isCollapsed ? "" : "rotate-180"}`} />
                  </button>
                )}
              </div>
              <button
                type="button"
                onClick={() => setConfirmDelete(i)}
                aria-label="Удалить элемент"
                className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors shrink-0"
              >
                <Trash2 size={16} />
              </button>
            </div>
            {collapsible ? (
              <div
                className="grid transition-[grid-template-rows] duration-300 ease-out"
                style={{ gridTemplateRows: isCollapsed ? "0fr" : "1fr" }}
              >
                <div className="overflow-hidden">
                  <div className="px-4 pb-4">
                    {renderItem(item, i, (updated) => updateItem(i, updated))}
                  </div>
                </div>
              </div>
            ) : (
              <div className="px-4 pb-4">
                {renderItem(item, i, (updated) => updateItem(i, updated))}
              </div>
            )}
          </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-gold/40 bg-gold/5 mb-3"
            style={{ height: collapsible ? 48 : dragSize.h }}
          />
        );
      }

      const item = items[i];
      const dragTitle = getItemTitle?.(item, i) || `#${i + 1}`;
      elements.push(
        <div
          key={i}
          ref={(el) => { itemRefs.current[i] = el; }}
          className="rounded-lg border border-white/10 bg-neutral-900/50 mb-3 transition-colors"
        >
          {collapsible ? (
            <div className="flex items-center gap-2 p-4">
              <GripVertical size={16} className="text-neutral-500 shrink-0" />
              <span className="text-sm font-medium text-neutral-300 truncate">{dragTitle}</span>
              {getItemBadge?.(item, i)}
            </div>
          ) : (
            <>
              <div className="flex items-start justify-between gap-2 p-4 pb-0 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)}
                  aria-label="Перетащить для сортировки"
                  role="button"
                >
                  <GripVertical size={16} />
                </div>
                <button
                  type="button"
                  onClick={() => removeItem(i)}
                  aria-label="Удалить элемент"
                  className="rounded p-1 text-neutral-500 hover:text-red-400 transition-colors"
                >
                  <Trash2 size={16} />
                </button>
              </div>
              <div className="px-4 pb-4">
                {renderItem(item, i, (updated) => updateItem(i, updated))}
              </div>
            </>
          )}
        </div>
      );
      visualIndex++;
    }

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

    return elements;
  }

  return (
    <div>
      {(label || (collapsible && items.length > 1)) && (
        <div className="flex items-center justify-between mb-3">
          {label ? <h3 className="text-sm font-medium text-neutral-300">{label}</h3> : <div />}
          {collapsible && items.length > 1 && (() => {
            const allCollapsed = collapsed.size >= items.length;
            return (
              <button
                type="button"
                onClick={() => allCollapsed ? setCollapsed(new Set()) : setCollapsed(new Set(items.map((_, i) => i)))}
                className="rounded p-1 text-neutral-500 hover:text-white transition-colors"
                title={allCollapsed ? "Развернуть все" : "Свернуть все"}
              >
                <ChevronsUpDown size={16} className={`transition-transform duration-200 ${allCollapsed ? "" : "rotate-90"}`} />
              </button>
            );
          })()}
        </div>
      )}

      {addPosition === "top" && (
        <button
          type="button"
          onClick={() => {
            onChange([createItem(), ...items]);
            setNewItemIndex(0);
            // Shift collapsed indices and ensure new item is expanded
            setCollapsed(prev => {
              const next = new Set<number>();
              for (const idx of prev) next.add(idx + 1);
              return next;
            });
          }}
          className="mb-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>
      )}

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

      {addPosition === "bottom" && (
        <button
          type="button"
          onClick={() => {
            onChange([...items, createItem()]);
            setNewItemIndex(items.length);
            setCollapsed(prev => { const next = new Set(prev); next.delete(items.length); return next; });
          }}
          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-gold/60 bg-neutral-900/95 shadow-2xl shadow-gold/20 flex items-center gap-3 px-4">
              <GripVertical size={16} className="text-gold shrink-0" />
              <span className="text-sm text-neutral-300">{collapsible && dragIndex !== null ? (getItemTitle?.(items[dragIndex], dragIndex) || "Перемещение...") : "Перемещение элемента..."}</span>
            </div>
          </div>,
          document.body
        )}

      <ConfirmDialog
        open={confirmDelete !== null}
        title="Удалить элемент?"
        message="Это действие нельзя отменить."
        onConfirm={() => { if (confirmDelete !== null) removeItem(confirmDelete); setConfirmDelete(null); }}
        onCancel={() => setConfirmDelete(null)}
      />
    </div>
  );
}
