/**
 * Перенос рейса в другой период/оборудование: drag с ручки, удержание 1–2 с над целью, выбор позиции.
 * Отмена: отпустить не над целью / Escape; другой период или оборудование — снова удержать над нужной карточкой или заново перетащить с ручки.
 */
const LONG_HOVER_MS = 1400;
const MOBILE_TRANSFER_MQ = "(max-width: 768px)";
const MOBILE_TRANSFER_LONG_PRESS_MS = 750;
const MOBILE_TRANSFER_MOVE_CANCEL_PX = 14;
const TRANSFER_MIME = "application/x-wesp-recipe-transfer";

export function attachRecipePeriodTransfer({
  getCurrentDispenserType,
  getSelectedDispenser,
  getSelectedPeriod,
  selectDispenser,
  selectPeriod,
  loadRecipes,
  loadPeriods,
  notyf,
}) {
  const recipesList = document.getElementById("recipesList");

  let session = null;
  let slotPick = false;
  let transferInFlight = false;
  let longHoverState = { kind: null, id: null, since: 0, fired: false };
  let rafHoverId = null;
  let listMutationTimer = null;

  function clearLongHover() {
    longHoverState = { kind: null, id: null, since: 0, fired: false };
    if (rafHoverId != null) {
      cancelAnimationFrame(rafHoverId);
      rafHoverId = null;
    }
  }

  function isTransferAllowed() {
    return (
      typeof getCurrentDispenserType === "function" &&
      getCurrentDispenserType() === "dispenser" &&
      getSelectedPeriod()
    );
  }

  function clearEligibleHighlights() {
    document.querySelectorAll(".recipe-transfer-eligible").forEach((el) => {
      el.classList.remove("recipe-transfer-eligible");
    });
  }

  function applyEligibleHighlights() {
    clearEligibleHighlights();
    if (!session || slotPick) return;
    document.querySelectorAll("#dispensersList .list-item[data-dispenser-id]").forEach((el) => {
      if (el.dataset.dispenserType === "mill") return;
      el.classList.add("recipe-transfer-eligible");
    });
    document.querySelectorAll("#periodsList .list-item[data-period-id]").forEach((el) => {
      const pid = el.dataset.periodId;
      if (
        String(pid) === String(session.fromPeriodId) &&
        String(getSelectedDispenser()) === String(session.fromDispenserId)
      ) {
        return;
      }
      el.classList.add("recipe-transfer-eligible");
    });
  }

  function startSession(recipeId, fromDispenserId, fromPeriodId) {
    clearSlotPickUi();
    session = { recipeId, fromDispenserId, fromPeriodId };
    slotPick = false;
    /* Подсветку целей не включаем здесь — иначе reorder в том же периоде блокируется с первого mousedown. */
  }

  function setMobileTransferOverlay(active) {
    if (!globalThis.matchMedia?.("(max-width: 768px)")?.matches) return;
    document.body.classList.toggle("recipe-transfer-session", !!active);
  }

  let pointerCaptureEl = null;
  let pointerCaptureId = null;
  let pendingDragItem = null;

  function releaseTransferPointerCapture() {
    if (pointerCaptureEl && pointerCaptureId != null) {
      try {
        pointerCaptureEl.releasePointerCapture(pointerCaptureId);
      } catch (_) {
        /* ignore */
      }
    }
    pointerCaptureEl = null;
    pointerCaptureId = null;
  }

  function clearTransferSessionState({ emitDragCancel = true } = {}) {
    if (listMutationTimer != null) {
      clearTimeout(listMutationTimer);
      listMutationTimer = null;
    }
    clearMobilePressState();
    releaseTransferPointerCapture();
    pendingDragItem = null;
    mouseSessionActive = false;
    session = null;
    slotPick = false;
    pointerSessionId = null;
    document.body.classList.remove("recipe-period-transfer-active");
    document.body.classList.remove("recipe-mobile-transfer-armed");
    setMobileTransferOverlay(false);
    clearEligibleHighlights();
    clearSlotPickUi();
    clearLongHover();
    if (emitDragCancel) {
      recipesList?.dispatchEvent(
        new CustomEvent("recipe-list-drag-cancel", { bubbles: true })
      );
    }
  }

  function endSession() {
    clearTransferSessionState({ emitDragCancel: true });
  }

  function clearSlotPickUi() {
    document.querySelectorAll(".recipe-transfer-slot").forEach((n) => n.remove());
  }

  function isValidDropTarget(toDispenser, toPeriod) {
    if (!toDispenser || !toPeriod || !session) return false;
    return !(
      String(toDispenser) === String(session.fromDispenserId) &&
      String(toPeriod) === String(session.fromPeriodId)
    );
  }

  /** Позиция вставки по Y относительно карточек (без учёта кнопок-слотов). */
  function insertIndexFromRecipesListDrop(listRoot, e) {
    if (!listRoot) return 0;
    const y = e.clientY;
    const items = [...listRoot.querySelectorAll(":scope > .list-item[data-recipe-id]")];
    for (let i = 0; i < items.length; i++) {
      const r = items[i].getBoundingClientRect();
      if (y < r.top + r.height / 2) return i;
    }
    return items.length;
  }

  async function commitTransfer(toIndex) {
    if (!session || transferInFlight) return;
    transferInFlight = true;
    const sess = session;
    const toDispenser = getSelectedDispenser();
    const toPeriod = getSelectedPeriod();
    try {
      if (!sess || !toDispenser || !toPeriod || !isValidDropTarget(toDispenser, toPeriod)) {
        endSession();
        return;
      }
      const url = `/api/feed_dispensers/${encodeURIComponent(toDispenser)}/periods/${encodeURIComponent(toPeriod)}/recipes/${encodeURIComponent(sess.recipeId)}/transfer`;
      const r = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          from_dispenser_id: sess.fromDispenserId,
          from_period_id: sess.fromPeriodId,
          to_index: toIndex,
        }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) {
        const msg = j.message || "Ошибка переноса";
        if (notyf) {
          if (r.status === 409) {
            if (typeof notyf.warning === "function") notyf.warning(msg);
            else notyf.error(msg);
          } else {
            notyf.error(msg);
          }
        }
        clearSlotPickUi();
        slotPick = false;
        if (r.status === 409) {
          try {
            if (typeof loadPeriods === "function") await loadPeriods(toDispenser);
            if (typeof loadRecipes === "function") await loadRecipes(toPeriod);
          } catch (_) {
            /* ignore */
          }
        }
        endSession();
        return;
      }
      clearSlotPickUi();
      slotPick = false;
      if (notyf) notyf.success(j.message || "Рейс перенесён");
      if (typeof loadPeriods === "function") await loadPeriods(toDispenser);
      if (typeof loadRecipes === "function") await loadRecipes(toPeriod);
      endSession();
    } catch (err) {
      console.error(err);
      if (notyf) notyf.error(err.message, { fallback: "Ошибка переноса" });
      endSession();
    } finally {
      transferInFlight = false;
    }
  }

  function scheduleHoverCheck() {
    if (rafHoverId != null) return;
    const tick = () => {
      rafHoverId = null;
      if (!session || longHoverState.kind == null) return;
      const elapsed = performance.now() - longHoverState.since;
      if (!longHoverState.fired && elapsed >= LONG_HOVER_MS) {
        longHoverState.fired = true;
        void fireLongHover(longHoverState.kind, longHoverState.id);
        return;
      }
      if (!longHoverState.fired && session) {
        rafHoverId = requestAnimationFrame(tick);
      }
    };
    rafHoverId = requestAnimationFrame(tick);
  }

  function trackLongHover(kind, id) {
    if (!session) return;
    if (longHoverState.kind !== kind || longHoverState.id !== id) {
      longHoverState = { kind, id, since: performance.now(), fired: false };
      applyEligibleHighlights();
    }
    setMobileTransferOverlay(true);
    scheduleHoverCheck();
  }

  async function fireLongHover(kind, id) {
    if (!session) return;
    transferHoverCommitPending = true;
    try {
      await fireLongHoverImpl(kind, id);
    } finally {
      transferHoverCommitPending = false;
    }
  }

  async function fireLongHoverImpl(kind, id) {
    if (!session) return;
    if (kind === "dispenser") {
      if (String(id) === String(getSelectedDispenser())) return;
      await selectDispenser(id);
      document.body.classList.add("recipe-period-transfer-active");
      applyEligibleHighlights();
      return;
    }
    if (kind === "period") {
      if (
        String(id) === String(session.fromPeriodId) &&
        String(getSelectedDispenser()) === String(session.fromDispenserId)
      ) {
        if (notyf) notyf.error("Выберите другой период");
        return;
      }
      if (slotPick && String(id) === String(getSelectedPeriod())) return;
      await selectPeriod(id);
      document.body.classList.add("recipe-period-transfer-active");
      enterSlotPickMode();
    }
  }

  function enterSlotPickMode() {
    slotPick = true;
    clearEligibleHighlights();
    clearLongHover();
    setMobileTransferOverlay(true);
    requestAnimationFrame(() => injectSlots());
  }

  /** Список рейсов перерисован (loadRecipes, смена периода, перестановка) — убрать «Вставить здесь», если они уже не в DOM. */
  function syncSlotPickAfterListMutation() {
    if (!session || !slotPick) return;
    const list = document.getElementById("recipesList");
    if (!list || list.querySelector(".recipe-transfer-slot")) return;
    endSession();
  }

  function scheduleSyncAfterListMutation() {
    clearTimeout(listMutationTimer);
    listMutationTimer = setTimeout(() => {
      listMutationTimer = null;
      syncSlotPickAfterListMutation();
    }, 80);
  }

  if (recipesList) {
    new MutationObserver(() => scheduleSyncAfterListMutation()).observe(recipesList, {
      childList: true,
      subtree: false,
    });
  }

  function injectSlots() {
    clearSlotPickUi();
    const list = document.getElementById("recipesList");
    if (!list || !session) return;
    const items = [...list.querySelectorAll(":scope > .list-item[data-recipe-id]")];
    for (let i = 0; i <= items.length; i++) {
      const slot = document.createElement("button");
      slot.type = "button";
      slot.className = "recipe-transfer-slot";
      slot.dataset.insertIndex = String(i);
      slot.innerHTML =
        '<span class="recipe-transfer-slot-inner"><span class="recipe-transfer-slot-label">Вставить здесь</span></span>';
      slot.setAttribute("aria-label", `Вставить рейс на позицию ${i + 1}`);
      slot.addEventListener("click", onSlotClick);
      if (i < items.length) {
        list.insertBefore(slot, items[i]);
      } else {
        list.appendChild(slot);
      }
    }
  }

  function onSlotClick(e) {
    const slot = e.target.closest(".recipe-transfer-slot");
    if (!slot || !session) return;
    const toIndex = Number(slot.dataset.insertIndex);
    if (Number.isNaN(toIndex)) return;
    void commitTransfer(toIndex);
  }

  function tryBeginSessionFromPendingDrag(clientX, clientY) {
    if (session || !pendingDragItem) return false;
    const target = document.elementFromPoint(clientX, clientY);
    if (target?.closest("#recipesList .list-item[data-recipe-id]")) return false;
    const dItem = target?.closest("#dispensersList .list-item[data-dispenser-id]");
    const pItem = target?.closest("#periodsList .list-item[data-period-id]");
    if (!dItem && !pItem) return false;
    if (dItem?.dataset.dispenserType === "mill") return false;
    beginHandleSession(pendingDragItem);
    return true;
  }

  function onDocumentDragOver(e) {
    if (!session) {
      if (tryBeginSessionFromPendingDrag(e.clientX, e.clientY)) {
        handleSessionHoverAtPoint(e.clientX, e.clientY, e);
      }
      return;
    }
    handleSessionHoverAtPoint(e.clientX, e.clientY, e);
  }

  function handleSessionHoverAtPoint(clientX, clientY, dragEvent) {
    if (!session) return;

    if (slotPick && recipesList && recipesList.contains(document.elementFromPoint(clientX, clientY))) {
      if (dragEvent?.preventDefault) {
        dragEvent.preventDefault();
        if (dragEvent.dataTransfer) dragEvent.dataTransfer.dropEffect = "move";
      }
      return;
    }

    if (slotPick) return;

    const target = document.elementFromPoint(clientX, clientY);
    const dItem = target?.closest("#dispensersList .list-item[data-dispenser-id]");
    const pItem = target?.closest("#periodsList .list-item[data-period-id]");
    if (dItem) {
      if (dItem.dataset.dispenserType === "mill") {
        clearLongHover();
        return;
      }
      if (dragEvent?.preventDefault) {
        dragEvent.preventDefault();
        if (dragEvent.dataTransfer) dragEvent.dataTransfer.dropEffect = "move";
      }
      trackLongHover("dispenser", dItem.dataset.dispenserId);
      return;
    }
    if (pItem) {
      if (dragEvent?.preventDefault) {
        dragEvent.preventDefault();
        if (dragEvent.dataTransfer) dragEvent.dataTransfer.dropEffect = "move";
      }
      trackLongHover("period", pItem.dataset.periodId);
      return;
    }
    clearLongHover();
  }

  function handleSessionDropAtPoint(clientX, clientY, dragEvent) {
    if (!session) return false;

    const target = document.elementFromPoint(clientX, clientY);

    if (slotPick && recipesList && target && recipesList.contains(target)) {
      const toDispenser = getSelectedDispenser();
      const toPeriod = getSelectedPeriod();
      if (!isValidDropTarget(toDispenser, toPeriod)) {
        dragEvent?.preventDefault?.();
        dragEvent?.stopPropagation?.();
        endSession();
        return true;
      }
      dragEvent?.preventDefault?.();
      dragEvent?.stopPropagation?.();
      const toIndex = insertIndexFromRecipesListDrop(recipesList, { clientY });
      void commitTransfer(toIndex);
      return true;
    }

    if (slotPick) return false;

    const recipeDrop = target?.closest("#recipesList .list-item[data-recipe-id]");
    if (recipeDrop) return false;

    const dItem = target?.closest("#dispensersList .list-item[data-dispenser-id]");
    const pItem = target?.closest("#periodsList .list-item[data-period-id]");
    if (dItem || pItem) {
      dragEvent?.preventDefault?.();
      return true;
    }

    dragEvent?.preventDefault?.();
    endSession();
    return true;
  }

  function onDocumentDropCapture(e) {
    if (!session) return;
    handleSessionDropAtPoint(e.clientX, e.clientY, e);
  }

  function beginHandleSession(item) {
    if (!item || item.dataset.index === undefined) return;
    if (!isTransferAllowed()) return;
    const recipeId = item.dataset.recipeId;
    const fromDispenserId = getSelectedDispenser();
    const fromPeriodId = getSelectedPeriod();
    startSession(recipeId, fromDispenserId, fromPeriodId);
  }

  function onDragStartCapture(e) {
    const handle = e.target.closest(".recipe-drag-handle");
    if (!handle || !recipesList || !recipesList.contains(handle)) return;
    const item = handle.closest(".list-item[data-recipe-id]");
    if (!item) return;
    /* Сессию transfer не открываем сразу — иначе ломается повторный reorder в том же списке. */
    pendingDragItem = item;
    try {
      e.dataTransfer.setData(
        TRANSFER_MIME,
        JSON.stringify({
          recipeId: item.dataset.recipeId,
          fromDispenserId: getSelectedDispenser(),
          fromPeriodId: getSelectedPeriod(),
        })
      );
    } catch (_) {
      /* ignore */
    }
  }

  function onDragEndCapture(e) {
    const handle = e.target.closest(".recipe-drag-handle");
    if (!handle || !recipesList || !recipesList.contains(handle)) return;
    if (slotPick && session) return;
    pendingDragItem = null;
    releaseTransferPointerCapture();
    pointerSessionId = null;
    mouseSessionActive = false;
    if (recipesList.classList.contains("recipe-list-reorder-active")) {
      if (!slotPick) clearTransferSessionState({ emitDragCancel: false });
      return;
    }
    if (session) clearTransferSessionState({ emitDragCancel: true });
  }

  let pointerSessionId = null;
  let mouseSessionActive = false;
  let transferHoverCommitPending = false;
  let mobilePressState = null;

  function isMobileTransferUi() {
    return globalThis.matchMedia?.(MOBILE_TRANSFER_MQ)?.matches ?? false;
  }

  function clearMobilePressState() {
    if (mobilePressState?.longPressTimer) {
      clearTimeout(mobilePressState.longPressTimer);
    }
    mobilePressState = null;
  }

  function cancelMobilePressForReorder() {
    clearMobilePressState();
  }

  function armMobileTransferSession(item) {
    if (!item || !isTransferAllowed()) return;
    beginHandleSession(item);
    document.body.classList.add("recipe-mobile-transfer-armed");
    recipesList?.dispatchEvent(new CustomEvent("recipe-transfer-armed", { bubbles: true }));
    globalThis.navigator?.vibrate?.(12);
    if (notyf) {
      notyf.success("Перетащите рейс на период или оборудование");
    }
  }

  function onPointerDownCapture(e) {
    if (e.pointerType === "mouse" && e.button !== 0) return;
    const handle = e.target.closest(".recipe-drag-handle");
    if (!handle || !recipesList || !recipesList.contains(handle)) return;
    const item = handle.closest(".list-item[data-recipe-id]");
    if (!item) return;
    if (!isTransferAllowed()) return;

    if (isMobileTransferUi()) {
      clearMobilePressState();
      mobilePressState = {
        pointerId: e.pointerId,
        item,
        startX: e.clientX,
        startY: e.clientY,
        longPressTimer: globalThis.setTimeout(() => {
          if (!mobilePressState) return;
          if (recipesList?.classList.contains("recipe-list-reorder-active")) {
            clearMobilePressState();
            return;
          }
          mobilePressState.armed = true;
          pointerSessionId = mobilePressState.pointerId;
          armMobileTransferSession(mobilePressState.item);
        }, MOBILE_TRANSFER_LONG_PRESS_MS),
      };
      return;
    }

    /* Desktop: reorder владеет ручкой через pointer/HTML5; transfer — через dragstart + hover. */
  }

  function onPointerMoveCapture(e) {
    if (mobilePressState && e.pointerId === mobilePressState.pointerId && !mobilePressState.armed) {
      const dx = Math.abs(e.clientX - mobilePressState.startX);
      const dy = Math.abs(e.clientY - mobilePressState.startY);
      if (dx + dy >= MOBILE_TRANSFER_MOVE_CANCEL_PX) {
        clearMobilePressState();
      }
    }

    if (!session || pointerSessionId == null || e.pointerId !== pointerSessionId) return;
    if (globalThis.matchMedia?.(MOBILE_TRANSFER_MQ)?.matches && e.clientY < window.innerHeight * 0.38) {
      setMobileTransferOverlay(true);
    }
    handleSessionHoverAtPoint(e.clientX, e.clientY, null);
  }

  function onPointerUpCapture(e) {
    if (mobilePressState && e.pointerId === mobilePressState.pointerId) {
      const wasArmed = mobilePressState.armed;
      clearMobilePressState();
      if (!wasArmed) return;
    }

    const ownsPointer = pointerSessionId != null && e.pointerId === pointerSessionId;
    if (ownsPointer) {
      releaseTransferPointerCapture();
      pointerSessionId = null;
      mouseSessionActive = false;
    }
    if (!session) return;
    if (!ownsPointer) return;
    if (transferHoverCommitPending || (slotPick && session)) return;

    const target = document.elementFromPoint(e.clientX, e.clientY);
    const overRecipe = target?.closest("#recipesList .list-item[data-recipe-id]");
    if (overRecipe && !slotPick) {
      clearTransferSessionState({ emitDragCancel: true });
      return;
    }

    handleSessionDropAtPoint(e.clientX, e.clientY, e);
  }

  function onPointerCancelCapture(e) {
    if (mobilePressState && e.pointerId === mobilePressState.pointerId) {
      clearMobilePressState();
    }
    if (pointerSessionId == null || e.pointerId !== pointerSessionId) return;
    pointerSessionId = null;
    if (slotPick && session) return;
    endSession();
  }

  recipesList?.addEventListener("dragstart", onDragStartCapture, true);
  recipesList?.addEventListener("dragend", onDragEndCapture, true);
  recipesList?.addEventListener("recipe-reorder-active", () => {
    cancelMobilePressForReorder();
    pendingDragItem = null;
    /* Не шлём recipe-list-drag-cancel — иначе reorder обрывается в pointer-path. */
    if (!slotPick) clearTransferSessionState({ emitDragCancel: false });
  });
  function onMouseDownCapture(e) {
    /* Desktop mousedown-сессия ломала повторный reorder; transfer на десктопе — dragstart + dragover. */
    if (!isMobileTransferUi()) return;
    if (e.button !== 0 || pointerSessionId != null) return;
    const handle = e.target.closest(".recipe-drag-handle");
    if (!handle || !recipesList || !recipesList.contains(handle)) return;
    const item = handle.closest(".list-item[data-recipe-id]");
    if (!item || !isTransferAllowed()) return;
    beginHandleSession(item);
    mouseSessionActive = true;
  }

  function onMouseMoveCapture(e) {
    if (!session || !mouseSessionActive || slotPick) return;
    handleSessionHoverAtPoint(e.clientX, e.clientY, null);
  }

  function onMouseUpCapture(e) {
    if (!mouseSessionActive) return;
    mouseSessionActive = false;
    if (!session) return;
    if (transferHoverCommitPending || (slotPick && session)) return;
    const target = document.elementFromPoint(e.clientX, e.clientY);
    const overRecipe = target?.closest("#recipesList .list-item[data-recipe-id]");
    if (overRecipe && !slotPick) {
      endSession();
      return;
    }
    handleSessionDropAtPoint(e.clientX, e.clientY, e);
  }

  recipesList?.addEventListener("pointerdown", onPointerDownCapture, true);
  recipesList?.addEventListener("mousedown", onMouseDownCapture, true);
  document.addEventListener("mousemove", onMouseMoveCapture, true);
  document.addEventListener("mouseup", onMouseUpCapture, true);
  document.addEventListener("pointermove", onPointerMoveCapture, true);
  document.addEventListener("pointerup", onPointerUpCapture, true);
  document.addEventListener("pointercancel", onPointerCancelCapture, true);
  document.addEventListener("dragover", onDocumentDragOver, true);
  document.addEventListener("drop", onDocumentDropCapture, true);

  document.addEventListener("keydown", (e) => {
    if (e.key !== "Escape" || !session) return;
    endSession();
  });

  return { endSession };
}
