/**
 * Коррекция СВ% компонентов в плане — overlay в стиле K-hub.
 * Редактируется только СВ%; превью через /api/recipes/calculate.
 */
(function (global) {
  function todayIso() {
    return global.WespDailyPlanSkipDuration?.todayIso?.() || new Date().toISOString().slice(0, 10);
  }

  function escapeHtml(value) {
    return (
      global.WespDailyPlanSkipDuration?.escapeHtml?.(value) ||
      String(value ?? "").replace(/&/g, "&amp;").replace(/</g, "&lt;")
    );
  }

  function fmtNum(value, digits) {
    const n = Number(value);
    if (!Number.isFinite(n)) return "—";
    return n.toFixed(digits).replace(/\.?0+$/, "");
  }

  function roundWphNum(value) {
    const n = Number(value);
    if (!Number.isFinite(n)) return NaN;
    if (n > 0 && n < 0.01) return 0.01;
    return Math.round(n * 100) / 100;
  }

  function fmtRange(values, digits) {
    const nums = values.filter((n) => Number.isFinite(n));
    if (!nums.length) return "—";
    const min = Math.min(...nums);
    const max = Math.max(...nums);
    const a = fmtNum(min, digits);
    const b = fmtNum(max, digits);
    return a === b ? a : `${a}–${b}`;
  }

  function usageEntries(item) {
    const usages = Array.isArray(item.usages) ? item.usages : [];
    if (usages.length) {
      return usages.map((u) => ({
        wph: Number(u.masterWeightPerHead),
        dmPh: Number(u.masterDryMatterPerHead),
        dryMatterLocked: !!u.dryMatterLocked,
      }));
    }
    return [
      {
        wph: Number(item.masterWeightPerHead),
        dmPh: Number(item.masterDryMatterPerHead),
        dryMatterLocked: false,
      },
    ];
  }

  const PHYS_WEIGHT_HINT = "Этот компонент выдается строго по физическому весу";

  function itemIsDmEditable(item) {
    return itemHasLockedUsage(item);
  }

  function componentCellHtml(item) {
    const editable = itemIsDmEditable(item);
    const lockedCount = lockedUsageEntries(item).length;
    const trips = Number(item.usageCount);
    let meta = "";
    if (editable && lockedCount > 0) {
      meta = `<div class="zt-k-hub-plan-norms__meta">${lockedCount} рец. с замком СВ</div>`;
    } else if (!editable) {
      meta = `<div class="zt-k-hub-plan-norms__meta zt-k-hub-plan-norms__meta--weight">${escapeHtml(PHYS_WEIGHT_HINT)}</div>`;
    } else if (Number.isFinite(trips) && trips > 1) {
      meta = `<div class="zt-k-hub-plan-norms__meta">${trips} рейсов в плане</div>`;
    }
    const lockIcon = editable
      ? ""
      : `<i class="fas fa-lock zt-k-hub-plan-norms__weight-lock" title="${escapeHtml(PHYS_WEIGHT_HINT)}" aria-hidden="true"></i>`;
    return (
      `<div class="zt-k-hub-plan-norms__component">` +
      `<div class="zt-k-hub-plan-norms__name-row">` +
      lockIcon +
      `<div class="zt-k-hub-plan-norms__name">${escapeHtml(item.componentName)}</div>` +
      `</div>` +
      meta +
      "</div>"
    );
  }

  function flashWeightLockedHint(input) {
    const cell = input?.closest(".zt-k-hub-plan-norms__dm-cell");
    if (!cell) return;
    let hint = cell.querySelector("[data-norms-weight-hint]");
    if (!hint) {
      hint = document.createElement("span");
      hint.className = "zt-k-hub-plan-norms__weight-hint";
      hint.dataset.normsWeightHint = "1";
      hint.textContent = PHYS_WEIGHT_HINT;
      cell.appendChild(hint);
    }
    hint.classList.add("is-visible");
    clearTimeout(hint._hideTimer);
    hint._hideTimer = setTimeout(() => hint.classList.remove("is-visible"), 2800);
  }

  function notify(msg, type) {
    const n = global.WespZootechNotify?.createAdapter?.();
    if (!n) return;
    if (type === "error") n.error(msg, { skipRecord: true });
    else n.success(msg, { skipRecord: true });
  }

  function overlayHost() {
    return document.getElementById("zootechNotificationCenterModal") || document.body;
  }

  let overlayEl = null;
  let items = [];
  const rowPreviewTimers = new Map();
  let recalcSeq = 0;
  let lockedOnlyFilter = true;

  function itemHasLockedUsage(item) {
    return usageEntries(item).some((entry) => entry.dryMatterLocked);
  }

  function lockedUsageEntries(item) {
    return usageEntries(item).filter((entry) => entry.dryMatterLocked);
  }

  function clearRowPreviewTimers() {
    rowPreviewTimers.forEach((timer) => clearTimeout(timer));
    rowPreviewTimers.clear();
  }

  function closeModal() {
    clearRowPreviewTimers();
    recalcSeq += 1;
    if (overlayEl?.isConnected) overlayEl.remove();
    overlayEl = null;
  }

  function actionBtn(action, attrs, title, icon) {
    return (
      `<button type="button" class="btn btn-sm recipe-table-delete-btn zt-k-hub-plan-row__skip" ` +
      `data-${action}="1" ${attrs} title="${escapeHtml(title)}">` +
      `<i class="fas ${icon}" aria-hidden="true"></i></button>`
    );
  }

  function masterPreviewForItem(item) {
    const wphs = [];
    const dmPhs = [];
    for (const entry of usageEntries(item)) {
      if (Number.isFinite(entry.wph)) wphs.push(roundWphNum(entry.wph));
      if (Number.isFinite(entry.dmPh)) dmPhs.push(entry.dmPh);
    }
    return {
      dmPh: fmtRange(dmPhs, 4),
      wph: fmtRange(wphs, 2),
    };
  }

  function previewCompareHtml(before, after) {
    const was = before || "—";
    const now = after || "—";
    if (was === "—" && now === "—") return "—";
    if (was === now) {
      return `<span class="zt-k-hub-plan-norms-preview__now">${escapeHtml(now)}</span>`;
    }
    return (
      `<span class="zt-k-hub-plan-norms-preview zt-k-hub-plan-norms-preview--compare">` +
      `<span class="zt-k-hub-plan-norms-preview__was">${escapeHtml(was)}</span>` +
      `<span class="zt-k-hub-plan-norms-preview__now">${escapeHtml(now)}</span>` +
      `</span>`
    );
  }

  async function previewForItem(item, planPct) {
    const pct = Number(planPct);
    if (!Number.isFinite(pct) || pct < 0) return { dmPh: "—", wph: "—" };

    const lockedEntries = lockedUsageEntries(item);
    const wphs = [];
    const dmPhs = [];

    await Promise.all(
      lockedEntries.map(async (entry) => {
        if (!Number.isFinite(entry.dmPh)) return;
        try {
          const response = await fetch("/api/recipes/calculate", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              ingredients: [
                {
                  component_id: item.componentId,
                  dryMatter: pct,
                  dryMatterPerHead: entry.dmPh,
                },
              ],
              headsCount: 1,
              tripPercent: 100,
              calculateFromDryMatter: true,
            }),
          });
          if (!response.ok) return;
          const result = await response.json();
          const calc = result.ingredients?.[0];
          if (!calc) return;
          dmPhs.push(Number(entry.dmPh));
          const rawWph = calc.weightPerHead !== undefined ? calc.weightPerHead : calc.weight_per_head;
          if (rawWph != null) wphs.push(roundWphNum(rawWph));
        } catch (_) {
          /* ignore */
        }
      })
    );

    for (const entry of usageEntries(item)) {
      if (entry.dryMatterLocked) continue;
      if (!Number.isFinite(entry.wph)) continue;
      wphs.push(roundWphNum(entry.wph));
      if (pct >= 0) dmPhs.push(entry.wph * (pct / 100));
    }

    return {
      dmPh: fmtRange(dmPhs, 4),
      wph: fmtRange(wphs, 2),
    };
  }

  function planEditValue(item) {
    if (item.planDryMatterPct != null) return item.planDryMatterPct;
    return item.masterDryMatterPct ?? item.dryMatterPct;
  }

  function masterBaseDryMatterLabel(item) {
    const catalog = Number(item.dryMatterPct);
    if (Number.isFinite(catalog)) {
      return `${fmtNum(catalog, 2)}%`;
    }
    const min = Number(item.masterDryMatterPctMin);
    const max = Number(item.masterDryMatterPctMax);
    if (Number.isFinite(min) && Number.isFinite(max)) {
      const a = fmtNum(min, 2);
      const b = fmtNum(max, 2);
      return a === b ? `${a}%` : `${a}–${b}%`;
    }
    const master = Number(item.masterDryMatterPct);
    if (Number.isFinite(master)) return `${fmtNum(master, 2)}%`;
    return null;
  }

  function baseDryMatterHintHtml(item) {
    const label = masterBaseDryMatterLabel(item);
    if (!label) return "";
    return (
      `<span class="zt-k-hub-plan-norms__base" data-norms-base title="СВ% в справочнике компонентов">` +
      `База: ${escapeHtml(label)}</span>`
    );
  }

  function setRowPreviewLoading(row) {
    const dmPhEl = row.querySelector("[data-norms-preview-dmph]");
    const wphEl = row.querySelector("[data-norms-preview-wph]");
    if (dmPhEl) {
      dmPhEl.textContent = "…";
      dmPhEl.classList.remove("zt-k-hub-plan-norms-preview-cell--compare");
    }
    if (wphEl) {
      wphEl.textContent = "…";
      wphEl.classList.remove("zt-k-hub-plan-norms-preview-cell--compare");
    }
  }

  function setPreviewCell(el, before, after) {
    if (!el) return;
    const was = before || "—";
    const now = after || "—";
    if (was === now) {
      el.classList.remove("zt-k-hub-plan-norms-preview-cell--compare");
      el.innerHTML = `<span class="zt-k-hub-plan-norms-preview">${escapeHtml(now)}</span>`;
      return;
    }
    el.classList.add("zt-k-hub-plan-norms-preview-cell--compare");
    el.innerHTML = previewCompareHtml(was, now);
  }

  async function updateRowPreview(row, seq) {
    const componentId = row.dataset.componentId;
    const item = items.find((x) => String(x.componentId) === String(componentId));
    if (!item) return;
    const input = row.querySelector("[data-norms-input]");
    const dmPhEl = row.querySelector("[data-norms-preview-dmph]");
    const wphEl = row.querySelector("[data-norms-preview-wph]");
    const masterPrev = masterPreviewForItem(item);
    if (row.dataset.normsEditable !== "1") {
      if (dmPhEl) {
        dmPhEl.classList.remove("zt-k-hub-plan-norms-preview-cell--compare");
        dmPhEl.innerHTML = `<span class="zt-k-hub-plan-norms-preview">${escapeHtml(masterPrev.dmPh)}</span>`;
      }
      if (wphEl) {
        wphEl.classList.remove("zt-k-hub-plan-norms-preview-cell--compare");
        wphEl.innerHTML = `<span class="zt-k-hub-plan-norms-preview">${escapeHtml(masterPrev.wph)}</span>`;
      }
      return;
    }
    const raw = input?.value;
    if (raw === "" || raw == null) {
      setPreviewCell(dmPhEl, null, null);
      setPreviewCell(wphEl, null, null);
      return;
    }
    setRowPreviewLoading(row);
    const next = await previewForItem(item, raw);
    if (seq !== recalcSeq || !overlayEl?.contains(row)) return;
    setPreviewCell(dmPhEl, masterPrev.dmPh, next.dmPh);
    setPreviewCell(wphEl, masterPrev.wph, next.wph);
  }

  function scheduleRowPreview(row, debounceMs) {
    if (!row) return;
    const componentId = row.dataset.componentId;
    if (!componentId) return;
    const existing = rowPreviewTimers.get(componentId);
    if (existing) clearTimeout(existing);
    const seq = recalcSeq;
    const delay = debounceMs == null ? 300 : debounceMs;
    if (delay <= 0) {
      rowPreviewTimers.delete(componentId);
      updateRowPreview(row, seq).catch((err) => console.error("Ошибка при расчете:", err));
      return;
    }
    rowPreviewTimers.set(
      componentId,
      setTimeout(() => {
        rowPreviewTimers.delete(componentId);
        updateRowPreview(row, seq).catch((err) => console.error("Ошибка при расчете:", err));
      }, delay)
    );
  }

  function updateAllPreviews() {
    overlayEl?.querySelectorAll("[data-norms-row]").forEach((row) => scheduleRowPreview(row, 0));
  }

  function buildRowHtml(item) {
    const editable = itemIsDmEditable(item);
    const val = planEditValue(item);
    const inputVal = val != null && Number.isFinite(Number(val)) ? Number(val) : "";
    const undo = item.adjustedToday
      ? actionBtn("norms-undo", "", "Вернуть как в рецепте", "fa-undo")
      : "";
    const replaceBtn = actionBtn(
      "norms-replace",
      "",
      "Заменить компонент во всех рецептах",
      "fa-exchange-alt"
    );
    const applyBtn = editable ? actionBtn("norms-apply", "", "Сохранить для плана", "fa-check") : "";
    const rowMods = [
      item.adjustedToday ? "ingredient-row--adjusted" : "",
      editable ? "" : "ingredient-row--weight-locked",
    ]
      .filter(Boolean)
      .join(" ");
    const inputClass = editable
      ? "form-control zt-k-hub-plan-norms-input dry-matter-percent-input"
      : "form-control zt-k-hub-plan-norms-input zt-k-hub-plan-norms-input--locked dry-matter-percent-input";
    const inputAttrs = editable
      ? `data-norms-input aria-label="СВ% на план"`
      : `readonly tabindex="-1" data-norms-input data-norms-input-locked aria-label="СВ% — только чтение" aria-disabled="true" title="${escapeHtml(PHYS_WEIGHT_HINT)}"`;
    return (
      `<div class="zt-data-grid__row ingredient-row${rowMods ? " " + rowMods : ""}" ` +
      `data-norms-row data-component-id="${escapeHtml(item.componentId)}" ` +
      `data-norms-editable="${editable ? "1" : "0"}" role="row">` +
      `<div class="zt-data-grid__cell ingredient-detail-cell zt-k-hub-plan-norms__component-cell" data-label="Компонент" role="gridcell">` +
      componentCellHtml(item) +
      "</div>" +
      `<div class="zt-data-grid__cell ingredient-detail-cell dry-matter-percent zt-data-grid__cell--num" data-label="СВ, %" role="gridcell">` +
      `<div class="zt-k-hub-plan-norms__dm-cell">` +
      `<input type="number" class="${inputClass}" step="0.01" min="0" max="100" ` +
      `value="${inputVal === "" ? "" : escapeHtml(String(inputVal))}" inputmode="decimal" ${inputAttrs}>` +
      baseDryMatterHintHtml(item) +
      `</div></div>` +
      `<div class="zt-data-grid__cell ingredient-detail-cell zt-data-grid__cell--num" data-label="СВ/гол, кг" role="gridcell">` +
      `<span class="text-cell zt-k-hub-plan-norms-preview-cell" data-norms-preview-dmph>…</span></div>` +
      `<div class="zt-data-grid__cell ingredient-detail-cell zt-data-grid__cell--num" data-label="Вес/гол, кг" role="gridcell">` +
      `<span class="text-cell zt-k-hub-plan-norms-preview-cell" data-norms-preview-wph>…</span></div>` +
      `<div class="zt-data-grid__cell ingredient-detail-cell zt-data-grid__cell--actions recipe-table-actions-cell" data-label="Действия" role="gridcell">` +
      `<div class="d-flex align-items-center justify-content-end flex-nowrap recipe-table-row-actions" role="group">` +
      applyBtn +
      replaceBtn +
      undo +
      "</div></div></div>"
    );
  }

  function ensureOverlay() {
    closeModal();
    overlayEl = document.createElement("div");
    overlayEl.className = "zt-k-hub-plan-overlay zt-k-hub-plan-norms";
    overlayEl.innerHTML =
      '<div class="zt-k-hub-plan-subdialog zt-k-hub-plan-norms__dialog" role="dialog" aria-modal="true">' +
      '<div class="zt-k-hub-plan-subdialog__header">' +
      '<h3 class="zt-k-hub-plan-subdialog__title">Коррекция СВ% в плане</h3>' +
      '<button type="button" class="wesp-shell-bsmodal-close" data-norms-close aria-label="Закрыть">×</button>' +
      "</div>" +
      '<p class="zt-k-hub-plan-norms__lead">Только на выбранный срок. Рецепты в справочнике не меняются. Пересчёт кг/гол — только где в рецепте включён замок СВ.</p>' +
      '<label class="zt-k-hub-plan-replace__search-label">Поиск компонента</label>' +
      '<input type="search" class="form-control zt-k-hub-plan-replace__search" data-norms-search placeholder="Начните вводить название…" autocomplete="off">' +
      '<label class="zt-k-hub-plan-norms__filter">' +
      '<span class="zt-checkbox">' +
      '<input type="checkbox" class="zt-checkbox__input" data-norms-locked-only checked>' +
      '<span class="zt-checkbox__box" aria-hidden="true"></span>' +
      "</span>" +
      '<span class="zt-k-hub-plan-norms__filter-text">Только компоненты в рецептах с замком СВ</span></label>' +
      '<div class="zt-k-hub-plan-norms__table-wrap">' +
      '<div class="zt-data-grid zt-data-grid--norms" id="planNormsGrid" role="grid" aria-label="Коррекция норм">' +
      '<div class="zt-data-grid__head" role="row">' +
      '<div class="zt-data-grid__cell zt-data-grid__cell--head" role="columnheader">Компонент</div>' +
      '<div class="zt-data-grid__cell zt-data-grid__cell--head zt-data-grid__cell--num" role="columnheader">СВ, %</div>' +
      '<div class="zt-data-grid__cell zt-data-grid__cell--head zt-data-grid__cell--num" role="columnheader">СВ/гол, кг</div>' +
      '<div class="zt-data-grid__cell zt-data-grid__cell--head zt-data-grid__cell--num" role="columnheader">Вес/гол, кг</div>' +
      '<div class="zt-data-grid__cell zt-data-grid__cell--head zt-data-grid__cell--actions" role="columnheader">Действия</div>' +
      "</div>" +
      '<div class="zt-data-grid__body" data-norms-body role="rowgroup"></div>' +
      "</div></div></div>";

    overlayEl.querySelector("[data-norms-close]")?.addEventListener("click", closeModal);
    overlayEl.addEventListener("click", (e) => {
      if (e.target === overlayEl) closeModal();
    });
    overlayEl.querySelector("[data-norms-search]")?.addEventListener("input", renderRows);
    overlayEl.querySelector("[data-norms-locked-only]")?.addEventListener("change", (event) => {
      lockedOnlyFilter = !!event.target.checked;
      renderRows();
    });
    overlayEl.querySelector("[data-norms-body]")?.addEventListener("input", (event) => {
      const row = event.target.closest("[data-norms-row]");
      if (row && event.target.matches("[data-norms-input]:not([data-norms-input-locked])")) {
        scheduleRowPreview(row);
      }
    });
    overlayEl.querySelector("[data-norms-body]")?.addEventListener("click", onTableClick);
    overlayEl.setAttribute("data-daily-plan-overlay", "1");
    const host = overlayHost();
    host.querySelectorAll(".zt-k-hub-plan-overlay").forEach((el) => {
      if (el !== overlayEl) el.remove();
    });
    host.appendChild(overlayEl);
    return overlayEl;
  }

  function filteredItems() {
    let list = items;
    if (lockedOnlyFilter) {
      list = list.filter(itemHasLockedUsage);
    }
    const q = (overlayEl?.querySelector("[data-norms-search]")?.value || "").trim().toLowerCase();
    if (!q) return list;
    return list.filter((item) => {
      if ((item.componentName || "").toLowerCase().includes(q)) return true;
      return (item.recipes || []).some((name) => String(name).toLowerCase().includes(q));
    });
  }

  function renderRows() {
    const body = overlayEl?.querySelector("[data-norms-body]");
    if (!body) return;
    const rows = filteredItems();
    if (!rows.length) {
      body.innerHTML =
        '<div class="zt-k-hub-plan-replace__empty zt-k-hub-plan-norms__empty">' +
        (lockedOnlyFilter
          ? "Нет компонентов в рецептах с замком СВ на эту дату"
          : "Нет компонентов в плане на эту дату") +
        "</div>";
      return;
    }
    body.innerHTML = rows.map((item) => buildRowHtml(item)).join("");
    updateAllPreviews();
  }

  async function loadItems() {
    const dispenserId = global.WespDailyPlanPanel?.getSelectedDispenserId?.() || "";
    if (!dispenserId) {
      notify("Сначала выберите кормораздатчик в плане", "error");
      return false;
    }
    const date = global.WespDailyPlanPanel?.getSelectedPlanDate?.() || todayIso();
    const r = await fetch(
      "/api/daily-plan/component-norms?dispenser_id=" +
        encodeURIComponent(dispenserId) +
        "&date=" +
        encodeURIComponent(date),
      { credentials: "same-origin" }
    );
    if (!r.ok) {
      notify("Не удалось загрузить компоненты плана", "error");
      return false;
    }
    const data = await r.json();
    items = Array.isArray(data.items) ? data.items : [];
    return true;
  }

  async function applyRow(row) {
    if (row.dataset.normsEditable !== "1") {
      flashWeightLockedHint(row.querySelector("[data-norms-input-locked]"));
      return;
    }
    const componentId = row.dataset.componentId;
    const input = row.querySelector("[data-norms-input]");
    const raw = input?.value;
    if (raw === "" || raw == null) {
      notify("Введите СВ%", "error");
      return;
    }
    const value = Number(raw);
    if (!Number.isFinite(value) || value < 0 || value > 100) {
      notify("СВ% должно быть от 0 до 100", "error");
      return;
    }
    const skip = global.WespDailyPlanSkipDuration;
    if (!skip) return;
    const planDate = global.WespDailyPlanPanel?.getSelectedPlanDate?.() || todayIso();
    const payload = await skip.skipDurationBody(
      planDate,
      { componentId, dryMatter: value },
      "На какой срок сохранить коррекцию?",
      {
        overlayHost: overlayHost(),
        keepOverlays: [".zt-k-hub-plan-norms"],
        onError: (msg) => notify(msg, "error"),
      }
    );
    if (!payload) return;
    const r = await fetch("/api/daily-plan/adjustments/components", {
      method: "POST",
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    const data = await r.json().catch(() => ({}));
    if (!r.ok) {
      notify(data.message || "Ошибка сохранения", "error");
      return;
    }
    notify("Коррекция сохранена");
    await refreshAfterChange();
  }

  async function undoRow(row) {
    const componentId = row.dataset.componentId;
    const planDate = global.WespDailyPlanPanel?.getSelectedPlanDate?.() || todayIso();
    const r = await fetch(
      "/api/daily-plan/adjustments/components?component_id=" +
        encodeURIComponent(componentId) +
        "&date=" +
        encodeURIComponent(planDate),
      { method: "DELETE", credentials: "same-origin" }
    );
    if (!r.ok) {
      const data = await r.json().catch(() => ({}));
      notify(data.message || "Не удалось сбросить", "error");
      return;
    }
    notify("СВ% как в рецепте");
    await refreshAfterChange();
  }

  async function replaceRow(row) {
    const componentId = row.dataset.componentId;
    const item = items.find((x) => String(x.componentId) === String(componentId));
    const label = item?.componentName || "Компонент";
    const replaceModal = global.WespDailyPlanReplaceModal;
    const skip = global.WespDailyPlanSkipDuration;
    if (!replaceModal || !skip) return;
    const planDate = global.WespDailyPlanPanel?.getSelectedPlanDate?.() || todayIso();
    const dispenserId = global.WespDailyPlanPanel?.getSelectedDispenserId?.() || "";
    if (!dispenserId) {
      notify("Сначала выберите кормораздатчик в плане", "error");
      return;
    }
    const durationHost = overlayHost();
    await replaceModal.open({
      componentId,
      label,
      overlayHost: overlayHost(),
      keepOverlays: [".zt-k-hub-plan-norms"],
      subtitle: "Замена применится во всех рецептах плана, где используется этот компонент.",
      onError: (msg) => notify(msg, "error"),
      onPick: async (replacementComponentId, replacementName) => {
        const body = await skip.skipDurationBody(
          planDate,
          { componentId, replacementComponentId, dispenserId },
          "На какой срок заменить?",
          {
            overlayHost: durationHost,
            keepOverlays: [".zt-k-hub-plan-norms"],
            onError: (msg) => notify(msg, "error"),
          }
        );
        if (!body) return;
        const resp = await fetch("/api/daily-plan/replacements/components", {
          method: "POST",
          credentials: "same-origin",
          headers: { "Content-Type": "application/json", Accept: "application/json" },
          body: JSON.stringify(body),
        });
        const data = await resp.json().catch(() => ({}));
        if (!resp.ok) {
          throw new Error(data.message || "Не удалось заменить компонент");
        }
        const count = Number(data.count) || 0;
        notify(
          count > 1
            ? `Компонент заменён на «${replacementName}» в ${count} рецептах`
            : `Компонент заменён на «${replacementName}»`
        );
        await refreshAfterChange();
      },
    });
  }

  async function refreshAfterChange() {
    await loadItems();
    renderRows();
    global.WespDailyPlanPanel?.reload?.();
    if (typeof global.reloadAfterSkipRestore === "function") global.reloadAfterSkipRestore();
  }

  function onTableClick(event) {
    const lockedInput = event.target.closest("[data-norms-input-locked]");
    if (lockedInput) {
      event.preventDefault();
      flashWeightLockedHint(lockedInput);
      return;
    }
    const row = event.target.closest("[data-norms-row]");
    if (!row) return;
    if (event.target.closest("[data-norms-apply]")) {
      event.preventDefault();
      applyRow(row);
    } else if (event.target.closest("[data-norms-replace]")) {
      event.preventDefault();
      replaceRow(row);
    } else if (event.target.closest("[data-norms-undo]")) {
      event.preventDefault();
      undoRow(row);
    }
  }

  async function openModal() {
    lockedOnlyFilter = true;
    ensureOverlay();
    const ok = await loadItems();
    if (ok) renderRows();
  }

  global.openDailyPlanComponentNormsModal = openModal;
  global.closeDailyPlanComponentNormsModal = closeModal;
})(window);
