/* global React */

/* ============================================================
   useSortableZone — tiny HTML5 drag-and-drop sortable hook.
   - Reorders within a single zone (no cross-zone moves yet).
   - Persists order to localStorage under "paradise-panel-layout".
   - Returns { order, getProps, dragState, reset }.

   v25: drag-handle = the whole panel surface. Drag is suppressed
   if it originated on an interactive child (button/a/input/etc) or
   on opt-in opaque content (the shot-row .row-shot class), so
   buttons, links, sort/filter controls, and shot rows still work.
   ============================================================ */

const LS_KEY = "paradise-panel-layout";

function readStore() {
  try {
    const raw = localStorage.getItem(LS_KEY);
    return raw ? JSON.parse(raw) : {};
  } catch { return {}; }
}
function writeStore(obj) {
  try { localStorage.setItem(LS_KEY, JSON.stringify(obj)); } catch {}
}
function reconcile(saved, defaults) {
  if (!Array.isArray(saved)) return defaults.slice();
  const known = new Set(defaults);
  const filtered = saved.filter(id => known.has(id));
  for (const id of defaults) if (!filtered.includes(id)) filtered.push(id);
  return filtered;
}

/* Selectors that should NOT initiate a panel drag when the dragstart
   originates inside them. Standard interactive elements + a couple of
   project-specific opt-outs (shot rows, scrollable lists). */
const NON_DRAG_SELECTORS = [
  "button", "a", "input", "textarea", "select", "label",
  "[role=\"button\"]", "[contenteditable=\"true\"]",
  ".row-shot",                    // shot list rows (own click target)
  ".sb-q-row",                    // sidebar queue rows have their own meaning
  ".week-cell",                   // schedule day cells
  ".time-slot",                   // schedule time slot rows
  ".pipe-row-item",               // pipeline legend rows
  ".activity-row",                // recent activity rows
  ".media-thumb",                 // media thumbnails
  ".dropzone",                    // upload dropzone
  ".pdot",                        // pagination dots
  ".pipe-donut-wrap",             // donut graphic in pipeline card
  ".rc-iconbtn",                  // small chevron icon buttons
];

function shouldSuppressDrag(target) {
  if (!target || !target.closest) return false;
  for (const sel of NON_DRAG_SELECTORS) {
    if (target.closest(sel)) return true;
  }
  return false;
}

function useSortableZone(zoneId, defaultIds, { axis = "y" } = {}) {
  const [order, setOrder] = React.useState(() => {
    const saved = readStore()[zoneId];
    return reconcile(saved, defaultIds);
  });
  const [dragId, setDragId] = React.useState(null);
  const [overId, setOverId] = React.useState(null);
  const [overSide, setOverSide] = React.useState(null); // "before" | "after"

  // While a drag is in progress, freeze pointer events on panel children so
  // dragover/drop always reach the panel itself — not the buttons inside it.
  React.useEffect(() => {
    if (!dragId) return;
    document.body.classList.add("is-dnd-active");
    return () => document.body.classList.remove("is-dnd-active");
  }, [dragId]);

  React.useEffect(() => {
    setOrder(prev => reconcile(prev, defaultIds));
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [defaultIds.join("|")]);

  const persist = (next) => {
    const store = readStore();
    store[zoneId] = next;
    writeStore(store);
  };

  const reset = React.useCallback(() => {
    const store = readStore();
    delete store[zoneId];
    writeStore(store);
    setOrder(defaultIds.slice());
  }, [zoneId, defaultIds]);

  const moveBeforeOrAfter = (dragged, target, after) => {
    setOrder(prev => {
      const next = prev.filter(id => id !== dragged);
      const targetIdx = next.indexOf(target);
      if (targetIdx === -1) return prev;
      const insertAt = after ? targetIdx + 1 : targetIdx;
      next.splice(insertAt, 0, dragged);
      persist(next);
      return next;
    });
  };

  const computeSide = (e, currentTarget) => {
    const rect = currentTarget.getBoundingClientRect();
    return axis === "y"
      ? ((e.clientY - rect.top) > rect.height / 2 ? "after" : "before")
      : ((e.clientX - rect.left) > rect.width / 2 ? "after" : "before");
  };

  const getProps = (id) => ({
    id,
    axis,
    isDragging: dragId === id,
    isOver: overId === id && dragId && dragId !== id,
    overSide: overId === id ? overSide : null,
    panelProps: {
      onDragStart: (e) => {
        // Suppress drag if the gesture originates on an interactive child.
        if (shouldSuppressDrag(e.target)) {
          e.preventDefault();
          return;
        }
        setDragId(id);
        try {
          e.dataTransfer.effectAllowed = "move";
          e.dataTransfer.setData("text/plain", id);
        } catch {}
      },
      onDragEnd: () => {
        setDragId(null);
        setOverId(null);
        setOverSide(null);
      },
      onDragEnter: (e) => {
        if (!dragId || dragId === id) return;
        e.preventDefault();
        setOverId(id);
        setOverSide(computeSide(e, e.currentTarget));
      },
      onDragOver: (e) => {
        if (!dragId || dragId === id) return;
        e.preventDefault();
        e.dataTransfer.dropEffect = "move";
        const next = computeSide(e, e.currentTarget);
        setOverId(prev => (prev === id ? prev : id));
        setOverSide(prev => (prev === next ? prev : next));
      },
      onDragLeave: (e) => {
        if (e.currentTarget.contains(e.relatedTarget)) return;
        setOverId(prev => (prev === id ? null : prev));
        setOverSide(prev => (overId === id ? null : prev));
      },
      onDrop: (e) => {
        if (!dragId || dragId === id) return;
        e.preventDefault();
        const after = computeSide(e, e.currentTarget) === "after";
        moveBeforeOrAfter(dragId, id, after);
        setDragId(null);
        setOverId(null);
        setOverSide(null);
      },
    },
  });

  return { order, getProps, dragId, overId, overSide, reset };
}

/* ---- SortablePanel: whole-surface drag handle ---- */
function SortablePanel({
  id, axis, isDragging, isOver, overSide, panelProps, children,
  className = "", as: Tag = "div",
}) {
  const cls = [
    "sortable-panel",
    "axis-" + axis,
    isDragging ? "is-dragging" : "",
    isOver ? "is-drop-target" : "",
    isOver && overSide === "before" ? "is-drop-before" : "",
    isOver && overSide === "after"  ? "is-drop-after"  : "",
    className,
  ].filter(Boolean).join(" ");

  return (
    <Tag
      className={cls}
      draggable
      {...panelProps}
    >
      {children}
    </Tag>
  );
}

function resetAllPanelLayouts() {
  try { localStorage.removeItem(LS_KEY); } catch {}
  window.dispatchEvent(new Event("paradise-layout-reset"));
}

Object.assign(window, { useSortableZone, SortablePanel, resetAllPanelLayouts });
