(function (global) {
  "use strict";

  const INDICATOR_LABELS = {
    dry_matter: "Сухое вещество",
    dm_main: "СВ — основной корм",
    oe: "ОЭ — КРС",
    nel: "ЧЭЛ — КРС",
    nel_per_kg_dm: "ЧЭЛ/кг СВ",
    crude_protein: "Сырой протеин",
    rup_per_kg_dm: "Нер. СП / кг СВ",
    insoluble_protein: "Нерастворимый протеин",
    usp: "уСП",
    usp_pct_dm: "% уСП/кг СВ",
    ndf: "Сырая клетчатка",
    structural_fiber: "Структур. клетчатка",
    crude_fat: "Сырой жир",
    rnb: "RNB",
    calcium: "Ca",
    phosphorus: "P",
    magnesium: "Mg",
    sodium: "Na",
    dcab: "DCAB",
    sugar_starch: "Сахар и крахмал",
    insoluble_starch: "Нераств. крахмал",
    sugar: "Сахар",
    starch: "Крахмал",
    carotene: "Каротин",
  };

  let state = null;
  let recipeId = null;
  let components = [];
  let profiles = [];
  let normCatalog = [];

  async function loadNormCatalog() {
    if (normCatalog.length) return normCatalog;
    try {
      const data = await api("/api/lab/norm-indicators");
      normCatalog = data.indicators || [];
    } catch (_e) {
      normCatalog = [];
    }
    return normCatalog;
  }

  function indicatorMeta(key) {
    const fromCat = normCatalog.find((item) => item.key === key);
    if (fromCat) return fromCat;
    return { key, label: INDICATOR_LABELS[key] || key, unit: "" };
  }

  function mergeIndicatorsForDisplay(calcRows, norms) {
    const byKey = new Map();
    for (const ind of calcRows || []) {
      if (ind?.key) byKey.set(ind.key, { ...ind });
    }
    for (const key of Object.keys(norms || {})) {
      const bounds = norms[key] || {};
      if (bounds.min == null && bounds.max == null) continue;
      const meta = indicatorMeta(key);
      const existing = byKey.get(key);
      if (existing) {
        existing.min = bounds.min ?? existing.min;
        existing.max = bounds.max ?? existing.max;
        existing.label = existing.label || meta.label;
        existing.unit = existing.unit || meta.unit || "";
      } else {
        byKey.set(key, {
          key,
          label: meta.label,
          unit: meta.unit || "",
          content: null,
          min: bounds.min,
          max: bounds.max,
          diff: null,
        });
      }
    }
    return [...byKey.values()];
  }

  async function api(path, opts) {
    const resp = await fetch(path, opts);
    const data = await resp.json().catch(() => ({}));
    if (!resp.ok) throw new Error(data.message || `Наука, чёрт возьми — ошибка ${resp.status}`);
    return data;
  }

  function labError(message) {
    global.WespLabHeisenbergPrime?.error?.(message, "sandbox");
  }

  function escapeHtml(s) {
    return String(s ?? "")
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;");
  }

  function formatNum(v) {
    if (v == null || v === "") return "—";
    const n = Number(v);
    if (Number.isNaN(n)) return "—";
    return Math.abs(n) >= 100 ? n.toFixed(1) : n.toFixed(3).replace(/\.?0+$/, "");
  }

  function formatKgInput(v) {
    if (v == null || v === "") return "";
    const n = Number(v);
    if (Number.isNaN(n)) return "";
    return String(Math.round(n * 100) / 100);
  }

  function diffClass(diff) {
    if (diff == null || Number.isNaN(Number(diff))) return "lab-sandbox-diff--na";
    const d = Number(diff);
    if (Math.abs(d) < 0.001) return "lab-sandbox-diff--ok";
    return "lab-sandbox-diff--bad";
  }

  async function loadCatalogs(rationType) {
    const [compResp, profResp] = await Promise.all([
      components.length ? Promise.resolve(components) : fetch("/api/components").then((r) => r.json()),
      api(`/api/lab/animal-profiles${rationType ? `?ration_type=${encodeURIComponent(rationType)}` : ""}`),
    ]);
    if (Array.isArray(compResp)) components = compResp;
    profiles = profResp.profiles || [];
  }

  async function loadRecipes() {
    try {
      const data = await api("/api/lab/recipes");
      return data.recipes || [];
    } catch (_e) {
      return [];
    }
  }

  function componentOptions(selectedId) {
    const opts = ['<option value="">— реагент —</option>'];
    components.forEach((c) => {
      const sel = String(c.id) === String(selectedId) ? " selected" : "";
      opts.push(`<option value="${escapeHtml(c.id)}"${sel}>${escapeHtml(c.name)}</option>`);
    });
    return opts.join("");
  }

  function profileOptions(selectedId) {
    const opts = ['<option value="">— стандарт чистоты —</option>'];
    profiles.forEach((p) => {
      const sel = String(p.id) === String(selectedId) ? " selected" : "";
      const key = p.profileKey ? `[${p.profileKey}] ` : "";
      opts.push(
        `<option value="${escapeHtml(p.id)}"${sel}>${escapeHtml(key)}${escapeHtml(p.label)} (${escapeHtml(p.rationType)})</option>`
      );
    });
    return opts.join("");
  }

  function hasNormBounds(ind) {
    return ind != null && (ind.min != null || ind.max != null);
  }

  function formatNormBounds(bounds) {
    const b = bounds || {};
    const min = b.min != null ? formatNum(b.min) : null;
    const max = b.max != null ? formatNum(b.max) : null;
    if (min != null && max != null) return `${min}–${max}`;
    if (max != null) return `≤ ${max}`;
    if (min != null) return `≥ ${min}`;
    return "—";
  }

  function formatIndicatorNorm(ind) {
    if (!hasNormBounds(ind)) {
      return '<span class="lab-sandbox-norm--empty" title="Для этого стандарта нет границы">вне стандарта</span>';
    }
    return formatNormBounds(ind);
  }

  function formatIndicatorDiff(ind) {
    if (!hasNormBounds(ind)) return "—";
    return formatNum(ind.diff);
  }

  function renderNormsPanel() {
    const el = document.getElementById("labSandboxNorms");
    if (!el) return;
    const norms = state?.norms || {};
    const keys = Object.keys(norms);
    if (!keys.length) {
      el.innerHTML = '<div class="lab-sandbox-empty">Выбери стандарт чистоты</div>';
      return;
    }
    let note = "";
    el.innerHTML =
      note +
      `<dl class="lab-sandbox-norms">` +
      keys
        .map((key) => {
          const b = norms[key] || {};
          const label = INDICATOR_LABELS[key] || key;
          return `<dt>${escapeHtml(label)}</dt><dd>${formatNormBounds(b)}</dd>`;
        })
        .join("") +
      `</dl>`;
  }

  function renderResultsPanel() {
    const el = document.getElementById("labSandboxResults");
    if (!el) return;
    const calcRows = state?.rationResults?.indicators || [];
    const totals = state?.rationResults?.totals || [];
    const norms = state?.norms || {};
    const indicators = mergeIndicatorsForDisplay(calcRows, norms);
    if (!indicators.length && !totals.length) {
      el.innerHTML = '<div class="lab-sandbox-empty">Нажми «Примени науку»</div>';
      return;
    }
    let html = "";
    if (!Object.keys(norms).length && calcRows.length) {
      html +=
        '<p class="small text-warning mb-2">Для полной матрицы показателей выберите «Стандарт чистоты» и снова нажмите «Примени науку».</p>';
    }
    if (totals.length) {
      html +=
        `<p class="small text-muted mb-1">Синий — значит чистый</p><ul class="small mb-2">` +
        totals.map((t) => `<li>${escapeHtml(t.label)}: ${formatNum(t.value)}</li>`).join("") +
        `</ul>`;
    }
    if (indicators.length) {
      const withNorm = indicators.filter((ind) => hasNormBounds(ind)).length;
      const rows = indicators.filter((ind) => hasNormBounds(ind) || ind.content != null);
      const sorted = rows.slice().sort((a, b) => {
        const an = hasNormBounds(a) ? 0 : 1;
        const bn = hasNormBounds(b) ? 0 : 1;
        if (an !== bn) return an - bn;
        return String(a.label).localeCompare(String(b.label), "ru");
      });
      html +=
        `<p class="small text-muted mb-1">Строк: ${sorted.length} (со стандартом: ${withNorm}). ≤/≥ — границы чистоты.</p>` +
        `<table><thead><tr><th>Показатель</th><th>Факт</th><th>Стандарт</th><th>Δ</th></tr></thead><tbody>` +
        sorted
          .map((ind) => {
            const dc = hasNormBounds(ind) ? diffClass(ind.diff) : "lab-sandbox-diff--na";
            return (
              `<tr>` +
              `<td>${escapeHtml(ind.label)}</td>` +
              `<td>${formatNum(ind.content)} ${escapeHtml(ind.unit || "")}</td>` +
              `<td>${formatIndicatorNorm(ind)}</td>` +
              `<td class="${dc}">${formatIndicatorDiff(ind)}</td>` +
              `</tr>`
            );
          })
          .join("") +
        `</tbody></table>`;
    }
    if (state?.calculatedAt) {
      html += `<p class="small text-muted mt-2 mb-0">Готово: ${escapeHtml(state.calculatedAt)}</p>`;
    }
    el.innerHTML = html;
  }

  function renderLines() {
    const tbody = document.getElementById("labSandboxLinesBody");
    if (!tbody) return;
    const lines = state?.lines || [];
    if (!lines.length) {
      tbody.innerHTML =
        `<tr><td colspan="6" class="lab-sandbox-empty">Партия пуста — добавь реагент или возьми из WESP</td></tr>`;
      return;
    }
    tbody.innerHTML = lines
      .map(
        (line, i) =>
          `<tr data-line-idx="${i}">` +
          `<td><select class="form-select form-select-sm" data-field="componentId">${componentOptions(line.componentId)}</select></td>` +
          `<td><input class="form-control form-control-sm" data-field="dailyKg" type="number" step="0.01" value="${formatKgInput(line.dailyKg)}"></td>` +
          `<td class="text-center"><input type="checkbox" data-field="inRation" ${line.inRation ? "checked" : ""}></td>` +
          `<td class="text-center"><input type="checkbox" data-field="inCompound" ${line.inCompound ? "checked" : ""}></td>` +
          `<td class="small text-muted">${line.pricePerKg != null ? formatNum(line.pricePerKg) : "—"}</td>` +
          `<td><button type="button" class="btn btn-sm btn-link text-danger p-0" data-action="remove-line" data-idx="${i}">&times;</button></td>` +
          `</tr>`
      )
      .join("");
  }

  function renderSetup() {
    const recipeSel = document.getElementById("labSandboxRecipe");
    const profileSel = document.getElementById("labSandboxProfile");
    const typeSel = document.getElementById("labSandboxRationType");
    if (profileSel) profileSel.innerHTML = profileOptions(state?.animalProfileId);
    if (typeSel && state?.rationType) typeSel.value = state.rationType;
    if (profileSel && state?.animalProfileId) profileSel.value = state.animalProfileId;
    if (recipeSel && recipeId) recipeSel.value = recipeId;
    const meta = document.getElementById("labSandboxMeta");
    if (meta && state) {
      meta.textContent = `${state.recipeName || ""} · ${state.headsPerTrip || 1} гол. · кг/сут на стадо · ${state.exists ? "формула есть" : "формула пуста — готовим?"}`;
    }
  }

  function collectLines() {
    const tbody = document.getElementById("labSandboxLinesBody");
    if (!tbody || !state) return state.lines || [];
    return (state.lines || []).map((line, i) => {
      const row = tbody.querySelector(`tr[data-line-idx="${i}"]`);
      if (!row) return line;
      const componentId = row.querySelector('[data-field="componentId"]')?.value || null;
      const comp = components.find((c) => String(c.id) === String(componentId));
      return {
        ...line,
        id: line.id,
        componentId,
        ingredientName: comp?.name || line.ingredientName,
        dailyKg: parseFloat(row.querySelector('[data-field="dailyKg"]')?.value) || null,
        inRation: row.querySelector('[data-field="inRation"]')?.checked,
        inCompound: row.querySelector('[data-field="inCompound"]')?.checked,
      };
    });
  }

  function collectPayload() {
    return {
      lines: collectLines(),
      rationType: document.getElementById("labSandboxRationType")?.value || state?.rationType,
      animalProfileId: document.getElementById("labSandboxProfile")?.value || null,
    };
  }

  async function reloadRation() {
    if (!recipeId) return;
    await loadNormCatalog();
    state = await api(`/api/lab/rations/${encodeURIComponent(recipeId)}`);
    if (state) {
      state.normsProfileKey = state.normsProfileKey || null;
      state.legacyNormsRemapped = Boolean(state.legacyNormsRemapped);
    }
    await loadCatalogs(state.rationType);
    if (state.legacyNormsRemapped && state.normsProfileKey) {
      const fp = profiles.find((p) => p.profileKey === state.normsProfileKey);
      if (fp) state.animalProfileId = fp.id;
    }
    renderSetup();
    renderLines();
    renderNormsPanel();
    renderResultsPanel();
  }

  async function ensureMaster() {
    if (!recipeId) return;
    if (state?.exists) return;
    const ok = await global.WespDialog?.confirm?.("Взять состав из WESP и начать готовку?", {
      title: "Нам нужно готовить",
    });
    if (ok) {
      await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/seed-from-execution`, { method: "POST" });
    } else {
      await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/ensure-empty`, { method: "POST" });
    }
    await reloadRation();
  }

  async function initRecipeSelect() {
    const sel = document.getElementById("labSandboxRecipe");
    if (!sel) return;
    const recipes = await loadRecipes();
    sel.innerHTML =
      '<option value="">— партия —</option>' +
      recipes.map((r) => `<option value="${escapeHtml(r.id)}">${escapeHtml(r.name)}</option>`).join("");
    const params = new URLSearchParams(global.location.search);
    const q = params.get("recipe");
    if (q) {
      sel.value = q;
      recipeId = q;
      await reloadRation();
      await ensureMaster();
    }
  }

  function bindEvents() {
    document.getElementById("labSandboxRecipe")?.addEventListener("change", async (ev) => {
      recipeId = ev.target.value || null;
      if (!recipeId) {
        state = null;
        renderLines();
        renderResultsPanel();
        return;
      }
      await reloadRation();
      await ensureMaster();
    });

    document.getElementById("labSandboxProfile")?.addEventListener("change", async (ev) => {
      const pid = ev.target.value;
      if (!pid) {
        if (state) state.norms = {};
        renderNormsPanel();
        return;
      }
      try {
        const profile = await api(`/api/lab/animal-profiles/${encodeURIComponent(pid)}`);
        if (state) {
          state.animalProfileId = pid;
          state.norms =
            profile.resolvedNorms ||
            profile.normsData?.resolvedIndicators ||
            profile.normsData?.indicators ||
            {};
          state.normsProfileKey = profile.normsProfileKey || profile.profileKey;
          state.legacyNormsRemapped = Boolean(profile.legacyNormsRemapped);
          if (profile.rationType) state.rationType = profile.rationType;
        }
        const typeSel = document.getElementById("labSandboxRationType");
        if (typeSel && profile.rationType) typeSel.value = profile.rationType;
        renderNormsPanel();
      } catch (e) {
        labError(e.message);
      }
    });

    document.getElementById("labSandboxRationType")?.addEventListener("change", async (ev) => {
      await loadCatalogs(ev.target.value);
      renderSetup();
    });

    document.getElementById("labSandboxLinesBody")?.addEventListener("click", (ev) => {
      const btn = ev.target.closest("[data-action='remove-line']");
      if (!btn || !state) return;
      const idx = parseInt(btn.getAttribute("data-idx"), 10);
      if (!Number.isNaN(idx)) {
        state.lines = (state.lines || []).filter((_, i) => i !== idx);
        renderLines();
      }
    });

    document.querySelector("[data-action='add-line']")?.addEventListener("click", () => {
      if (!state) state = { lines: [] };
      state.lines = [
        ...(state.lines || []),
        { componentId: "", ingredientName: "", dailyKg: 0, inRation: true, inCompound: false },
      ];
      renderLines();
    });

    document.querySelector("[data-action='save']")?.addEventListener("click", async () => {
      if (!recipeId) {
        labError("Сначала выбери партию — без этого не готовим");
        return;
      }
      try {
        await api(`/api/lab/rations/${encodeURIComponent(recipeId)}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(collectPayload()),
        });
        await reloadRation();
        global.WespZootechNotify?.createAdapter?.()?.success?.("Запомни это — формула сохранена");
      } catch (e) {
        labError(e.message);
      }
    });

    document.querySelector("[data-action='recalculate']")?.addEventListener("click", async () => {
      if (!recipeId) {
        labError("Сначала выбери партию — науку некуда применять");
        return;
      }
      try {
        await api(`/api/lab/rations/${encodeURIComponent(recipeId)}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(collectPayload()),
        });
        await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/recalculate`, { method: "POST" });
        await reloadRation();
        global.WespZootechNotify?.createAdapter?.()?.success?.("Применена наука — 99,1% чистоты");
      } catch (e) {
        labError(e.message);
      }
    });

    document.querySelector("[data-action='seed']")?.addEventListener("click", async () => {
      if (!recipeId) {
        labError("Сначала выбери партию — нечего брать с поля");
        return;
      }
      try {
        await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/seed-from-execution`, { method: "POST" });
        await reloadRation();
      } catch (e) {
        labError(e.message);
      }
    });

    document.querySelector("[data-action='apply']")?.addEventListener("click", async () => {
      if (!recipeId) {
        labError("Сначала выбери партию — некуда отправлять");
        return;
      }
      const ok = await global.WespDialog?.confirm?.("Отправить кг/сут из лаборатории в WESP?");
      if (!ok) return;
      try {
        await api(`/api/lab/rations/${encodeURIComponent(recipeId)}/apply-from-master`, { method: "POST" });
        global.WespZootechNotify?.createAdapter?.()?.success?.("Отправлено в WESP. Ты чертовски прав");
      } catch (e) {
        labError(e.message);
      }
    });
  }

  function init() {
    bindEvents();
    loadNormCatalog();
    initRecipeSelect();
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})(window);
