/**
 * Страница редактирования кластера: GET/PUT /api/v1/clusters/{name}/config.
 * Расширенный режим — форма по документации kind (networking, nodes, extraPortMappings, kubeadmConfigPatches).
 *
 * Автор: Сергей Антропов
 * Сайт: https://devops.org.ru
 */
(function () {
  "use strict";

  const body = document.body;
  const API = (body.dataset.apiBase || "/api/v1").replace(/\/$/, "");
  const clusterName = (body.getAttribute("data-cluster-name") || "").trim();
  if (!clusterName) return;

  /** @type {string} */
  let initialTag = "";
  /** @type {number | null} */
  let initialWorkers = null;
  /** @type {string} */
  let initialDescription = "";
  /** @type {string | null} */
  let loadedYamlSnapshot = null;
  /** @type {string} */
  let initialAdvancedSnapshot = "";

  /**
   * @typedef {object} AdvancedFormState
   * @property {string} apiVersion
   * @property {string} image
   * @property {number} controlPlanes
   * @property {number} workers
   * @property {string} ipFamily
   * @property {string} apiServerAddress
   * @property {string} apiServerPort
   * @property {string} podSubnet
   * @property {string} serviceSubnet
   * @property {string} kubeProxyMode
   * @property {boolean} disableDefaultCNI
   * @property {boolean} portEnabled
   * @property {string} hostPort
   * @property {number} containerPort
   * @property {string} portListenAddress
   * @property {string} portProtocol
   * @property {string} kubeadmPatch
   */

  function formatApiError(data, fallback) {
    if (!data) return fallback;
    if (typeof data.detail === "string") return data.detail;
    if (Array.isArray(data.detail)) {
      return data.detail
        .map(function (x) {
          return (x.msg || x) + (x.loc ? " (" + x.loc.join(".") + ")" : "");
        })
        .join("; ");
    }
    return fallback;
  }

  /**
   * @param {string} path
   * @param {RequestInit} [opts]
   */
  async function api(path, opts) {
    const url = path.startsWith("http") ? path : API + path;
    const r = await fetch(url, opts);
    const text = await r.text();
    var data;
    try {
      data = text ? JSON.parse(text) : null;
    } catch (e) {
      data = { raw: text };
    }
    if (!r.ok) {
      const msg = formatApiError(data, text || r.statusText);
      const err = new Error(msg);
      err.status = r.status;
      throw err;
    }
    return data;
  }

  /**
   * Собрать содержимое модалки: как на диске сохранилось и как это доходит до kind.
   * @param {boolean} updatedYaml
   * @param {boolean} registeredInKind
   * @returns {DocumentFragment}
   */
  function buildSaveInfoModalContent(updatedYaml, registeredInKind) {
    const frag = document.createDocumentFragment();
    if (!updatedYaml) {
      const p = document.createElement("p");
      p.textContent =
        "Сохранены поля без изменения kind-config.yaml (например, только описание в meta.json). " +
        "Топология кластера в kind и уже запущенные узлы от этого не меняются.";
      frag.appendChild(p);
      return frag;
    }
    const lead = document.createElement("p");
    lead.className = "cluster-edit-save-modal-lead";
    lead.textContent = "Файл kind-config.yaml и связанные поля meta.json записаны на диск.";
    frag.appendChild(lead);

    if (registeredInKind) {
      const pIntro = document.createElement("p");
      pIntro.textContent = "Кластер уже зарегистрирован в kind. Новый конфиг применяется так:";
      frag.appendChild(pIntro);
      const ul = document.createElement("ul");
      const lines = [
        "До нажатия «Старт» продолжают работать текущие узлы (старая топология и образы).",
        "При «Старт» на панели или на странице кластера выполняется пересоздание: удаление записи в kind без удаления каталога clusters/" +
          clusterName +
          "/, затем создание кластера по обновлённому YAML.",
        "«Стоп» перед «Старт» не обязателен — можно нажать «Старт» сразу после сохранения.",
      ];
      lines.forEach(function (line) {
        const li = document.createElement("li");
        li.textContent = line;
        ul.appendChild(li);
      });
      frag.appendChild(ul);
      const note = document.createElement("p");
      note.style.marginTop = "0.65rem";
      note.style.fontSize = "0.88rem";
      note.textContent =
        "Состояние Kubernetes внутри кластера при пересоздании начинается заново; kubeconfig в каталоге кластера обновится после успешного «Старт».";
      frag.appendChild(note);
    } else {
      const p2 = document.createElement("p");
      p2.textContent =
        "Кластер сейчас не в списке kind. При «Старт» он будет поднят по сохранённому kind-config.yaml (полный цикл создания, как при восстановлении из файла).";
      frag.appendChild(p2);
    }
    return frag;
  }

  function openClusterEditSaveModal(updatedYaml, registeredInKind) {
    const overlay = document.getElementById("cluster-edit-save-modal-overlay");
    const titleEl = document.getElementById("cluster-edit-save-modal-title");
    const bodyEl = document.getElementById("cluster-edit-save-modal-body");
    if (!overlay || !titleEl || !bodyEl) return;

    titleEl.textContent = updatedYaml ? "Конфигурация сохранена" : "Сохранено";
    bodyEl.replaceChildren();
    bodyEl.appendChild(buildSaveInfoModalContent(updatedYaml, registeredInKind));

    overlay.classList.remove("hidden");
    overlay.setAttribute("aria-hidden", "false");
    document.body.classList.add("modal-open");

    const okBtn = document.getElementById("cluster-edit-save-modal-ok");
    if (okBtn) okBtn.focus();
  }

  function closeClusterEditSaveModal() {
    const overlay = document.getElementById("cluster-edit-save-modal-overlay");
    if (!overlay) return;
    overlay.classList.add("hidden");
    overlay.setAttribute("aria-hidden", "true");
    document.body.classList.remove("modal-open");
  }

  /** Закрытие модалки после сохранения и переход на страницу кластера (только кнопка «Понятно»). */
  function onSaveInfoModalOk() {
    closeClusterEditSaveModal();
    window.location.href = "/cluster/" + encodeURIComponent(clusterName);
  }

  function wireSaveInfoModal() {
    const overlay = document.getElementById("cluster-edit-save-modal-overlay");
    const ok = document.getElementById("cluster-edit-save-modal-ok");
    if (!overlay || !ok) return;
    ok.addEventListener("click", onSaveInfoModalOk);
    overlay.addEventListener("click", function (ev) {
      if (ev.target === overlay) closeClusterEditSaveModal();
    });
    document.addEventListener("keydown", function (ev) {
      if (ev.key === "Escape" && !overlay.classList.contains("hidden")) {
        closeClusterEditSaveModal();
      }
    });
  }

  function wireVersionSelect(selId, inputId) {
    const sel = document.getElementById(selId);
    const verInput = document.getElementById(inputId);
    if (!sel || !verInput) return;
    sel.onchange = function () {
      if (!sel.value) return;
      if (inputId === "fld-adv-image") {
        verInput.value =
          sel.value === "latest" ? "kindest/node:latest" : "kindest/node:" + sel.value.replace(/^v/, "");
      } else {
        verInput.value = sel.value === "latest" ? "latest" : sel.value.replace(/^v/, "");
      }
    };
  }

  async function loadVersions() {
    const ids = [
      ["version-select-edit", "kubernetes_version_edit"],
      ["version-select-adv", "fld-adv-image"],
    ];
    ids.forEach(function (pair) {
      const sel = document.getElementById(pair[0]);
      if (sel) sel.innerHTML = "<option value=\"\">— загрузка —</option>";
    });
    try {
      const data = await api("/versions");
      ids.forEach(function (pair) {
        const sel = document.getElementById(pair[0]);
        if (!sel) return;
        sel.innerHTML = "";
        if (!data.tags || !data.tags.length) {
          sel.innerHTML = "<option value=\"\">(список пуст — введите вручную)</option>";
          return;
        }
        const opt0 = document.createElement("option");
        opt0.value = "";
        opt0.textContent = "— выберите тег —";
        sel.appendChild(opt0);
        data.tags.forEach(function (t) {
          const o = document.createElement("option");
          o.value = t;
          o.textContent = t === "latest" ? "latest (kindest/node)" : t;
          sel.appendChild(o);
        });
      });
      wireVersionSelect("version-select-edit", "kubernetes_version_edit");
      wireVersionSelect("version-select-adv", "fld-adv-image");
    } catch (e) {
      ids.forEach(function (pair) {
        const sel = document.getElementById(pair[0]);
        if (sel) sel.innerHTML = "<option value=\"\">(ошибка загрузки тегов)</option>";
      });
    }
  }

  /**
   * Разбор секции networking: (документация kind — cluster-wide networking).
   * @param {string} text
   * @returns {Pick<AdvancedFormState, 'ipFamily'|'apiServerAddress'|'apiServerPort'|'podSubnet'|'serviceSubnet'|'kubeProxyMode'|'disableDefaultCNI'>}
   */
  function parseNetworkingSection(text) {
    const out = {
      ipFamily: "",
      apiServerAddress: "",
      apiServerPort: "",
      podSubnet: "",
      serviceSubnet: "",
      kubeProxyMode: "",
      disableDefaultCNI: false,
    };
    const lines = text.split("\n");
    let i = 0;
    while (i < lines.length) {
      if (/^\s*networking:\s*$/.test(lines[i])) {
        i++;
        while (i < lines.length) {
          const L = lines[i];
          const t = L.trim();
          if (!t) {
            i++;
            continue;
          }
          if (/^[a-zA-Z]/.test(L) && !/^\s/.test(L)) break;
          const kv = L.match(/^\s{2}([a-zA-Z0-9]+):\s*(.*)$/);
          if (kv) {
            const k = kv[1];
            let v = kv[2].trim();
            if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
              v = v.slice(1, -1);
            }
            if (k === "ipFamily") out.ipFamily = v;
            else if (k === "apiServerAddress") out.apiServerAddress = v;
            else if (k === "apiServerPort") out.apiServerPort = v;
            else if (k === "podSubnet") out.podSubnet = v;
            else if (k === "serviceSubnet") out.serviceSubnet = v;
            else if (k === "kubeProxyMode") out.kubeProxyMode = v.replace(/^["']|["']$/g, "");
            else if (k === "disableDefaultCNI") out.disableDefaultCNI = v === "true";
          }
          i++;
        }
        break;
      }
      i++;
    }
    return out;
  }

  /**
   * @param {AdvancedFormState} f
   * @returns {string}
   */
  function buildNetworkingYaml(f) {
    const rows = [];
    if (f.ipFamily && f.ipFamily !== "ipv4") rows.push("  ipFamily: " + f.ipFamily);
    const addr = (f.apiServerAddress || "").trim();
    if (addr) rows.push('  apiServerAddress: "' + addr.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"');
    const ap = String(f.apiServerPort || "").trim();
    if (ap) {
      const pn = parseInt(ap, 10);
      if (pn >= 1 && pn <= 65535) rows.push("  apiServerPort: " + pn);
    }
    const ps = (f.podSubnet || "").trim();
    if (ps) rows.push('  podSubnet: "' + ps.replace(/"/g, '\\"') + '"');
    const ss = (f.serviceSubnet || "").trim();
    if (ss) rows.push('  serviceSubnet: "' + ss.replace(/"/g, '\\"') + '"');
    const kpm = (f.kubeProxyMode || "").trim();
    if (kpm) rows.push('  kubeProxyMode: "' + kpm.replace(/"/g, '\\"') + '"');
    if (f.disableDefaultCNI) rows.push("  disableDefaultCNI: true");
    if (!rows.length) return "";
    return "networking:\n" + rows.join("\n") + "\n";
  }

  /**
   * @param {string} text
   * @returns {AdvancedFormState}
   */
  function parseKindYamlToAdvancedForm(text) {
    /** @type {AdvancedFormState} */
    const out = {
      apiVersion: "kind.x-k8s.io/v1alpha4",
      image: "",
      controlPlanes: 1,
      workers: 0,
      ipFamily: "",
      apiServerAddress: "",
      apiServerPort: "",
      podSubnet: "",
      serviceSubnet: "",
      kubeProxyMode: "",
      disableDefaultCNI: false,
      portEnabled: false,
      hostPort: "",
      containerPort: 80,
      portListenAddress: "",
      portProtocol: "TCP",
      kubeadmPatch: "",
    };
    if (!text || !text.trim()) return out;
    const av = text.match(/^\s*apiVersion:\s*(\S+)/m);
    if (av) out.apiVersion = av[1].trim().replace(/['"]/g, "");
    const net = parseNetworkingSection(text);
    Object.assign(out, net);

    const lines = text.split("\n");
    let inNodes = false;
    let cp = 0;
    let wk = 0;
    /** @type {string | null} */
    let pendingRole = null;
    const images = [];
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      const t = line.trim();
      if (t === "nodes:" || t.startsWith("nodes:")) {
        inNodes = true;
        pendingRole = null;
        continue;
      }
      if (!inNodes) continue;
      if (t && !line.startsWith(" ") && !line.startsWith("\t")) break;
      const roleM = line.match(/^\s*-\s*role:\s*(\S+)/);
      if (roleM) {
        pendingRole = roleM[1];
        if (pendingRole === "control-plane") cp++;
        else if (pendingRole === "worker") wk++;
        continue;
      }
      const imgM = line.match(/^\s*image:\s*(.+)$/);
      if (imgM && pendingRole) {
        images.push(imgM[1].trim().replace(/['"]/g, ""));
      }
    }
    if (cp > 0) out.controlPlanes = cp;
    out.workers = wk;
    const uniq = [];
    images.forEach(function (im) {
      if (uniq.indexOf(im) < 0) uniq.push(im);
    });
    out.image = uniq[0] || "";

    const portSplit = text.split("extraPortMappings:");
    if (portSplit.length > 1) {
      const portBlock = portSplit[1];
      const hh = portBlock.match(/hostPort:\s*(\d+)/);
      const cc = portBlock.match(/containerPort:\s*(\d+)/);
      const la =
        portBlock.match(/listenAddress:\s*"([^"]+)"/) ||
        portBlock.match(/listenAddress:\s*'([^']+)'/);
      const pr = portBlock.match(/protocol:\s*(\w+)/);
      if (hh) {
        out.portEnabled = true;
        out.hostPort = hh[1];
        if (cc) out.containerPort = parseInt(cc[1], 10) || 80;
        if (la) out.portListenAddress = la[1];
        if (pr && /UDP|SCTP|TCP/i.test(pr[1])) out.portProtocol = pr[1].toUpperCase();
      }
    }

    const kubStart = text.indexOf("kubeadmConfigPatches:");
    if (kubStart >= 0) {
      const sub = text.slice(kubStart);
      const pipeM = sub.match(/kubeadmConfigPatches:\s*\n\s*-\s*\|\s*\n([\s\S]+)$/);
      if (pipeM) {
        const raw = pipeM[1];
        const linesPatch = raw.split("\n");
        const outLines = [];
        for (let j = 0; j < linesPatch.length; j++) {
          const ln = linesPatch[j];
          if (/^\s*-\s*role:\s*/.test(ln)) break;
          outLines.push(ln.replace(/^        /, ""));
        }
        out.kubeadmPatch = outLines.join("\n").replace(/\n+$/, "");
      }
    }
    return out;
  }

  function ensureApiVersionOption(apiVersion) {
    const sel = document.getElementById("fld-adv-api-version");
    if (!sel || !apiVersion) return;
    let found = false;
    sel.querySelectorAll("option").forEach(function (o) {
      if (o.value === apiVersion) found = true;
    });
    if (!found) {
      const o = document.createElement("option");
      o.value = apiVersion;
      o.textContent = apiVersion;
      sel.appendChild(o);
    }
  }

  /**
   * @param {AdvancedFormState} f
   * @returns {string}
   */
  function serializeAdvancedKindCluster(f) {
    const api = f.apiVersion || "kind.x-k8s.io/v1alpha4";
    const img = (f.image || "").trim();
    if (!img) throw new Error("Укажите образ узла (kindest/node:…).");
    const cp = Math.min(5, Math.max(1, parseInt(String(f.controlPlanes), 10) || 1));
    const wk = Math.min(20, Math.max(0, parseInt(String(f.workers), 10) || 0));

    const lines = ["kind: Cluster", "apiVersion: " + api];
    const netBlock = buildNetworkingYaml(f);
    if (netBlock) lines.push(netBlock.trimEnd());

    lines.push("nodes:");
    const firstExtras = [];
    if (f.portEnabled && f.hostPort) {
      const hp = parseInt(String(f.hostPort), 10);
      const ctp = parseInt(String(f.containerPort), 10) || 80;
      if (hp >= 1 && hp <= 65535) {
        firstExtras.push("    extraPortMappings:");
        firstExtras.push("      - containerPort: " + ctp);
        firstExtras.push("        hostPort: " + hp);
        const listen = (f.portListenAddress || "").trim();
        if (listen) {
          firstExtras.push('        listenAddress: "' + listen.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"');
        }
        const proto = (f.portProtocol || "TCP").toUpperCase();
        if (proto === "TCP" || proto === "UDP" || proto === "SCTP") {
          firstExtras.push("        protocol: " + proto);
        }
      }
    }
    const patch = (f.kubeadmPatch || "").trim();
    if (patch) {
      firstExtras.push("    kubeadmConfigPatches:");
      firstExtras.push("      - |");
      patch.split("\n").forEach(function (ln) {
        firstExtras.push("        " + ln);
      });
    }
    let i;
    for (i = 0; i < cp; i++) {
      lines.push("  - role: control-plane");
      lines.push("    image: " + img);
      if (i === 0 && firstExtras.length) {
        firstExtras.forEach(function (x) {
          lines.push(x);
        });
      }
    }
    for (i = 0; i < wk; i++) {
      lines.push("  - role: worker");
      lines.push("    image: " + img);
    }
    return lines.join("\n") + "\n";
  }

  /**
   * @returns {AdvancedFormState}
   */
  function readAdvancedFormFromDom() {
    const pick = function (id) {
      const el = document.getElementById(id);
      return el ? el.value : "";
    };
    const pickNum = function (id, def) {
      const el = document.getElementById(id);
      if (!el || !el.value.trim()) return def;
      const n = parseInt(el.value, 10);
      return Number.isNaN(n) ? def : n;
    };
    const pickChk = function (id) {
      const el = document.getElementById(id);
      return !!(el && el.checked);
    };
    return {
      apiVersion: pick("fld-adv-api-version") || "kind.x-k8s.io/v1alpha4",
      image: pick("fld-adv-image").trim(),
      controlPlanes: pickNum("fld-adv-cp", 1),
      workers: pickNum("fld-adv-workers", 0),
      ipFamily: pick("fld-adv-ip-family"),
      apiServerAddress: pick("fld-adv-api-addr"),
      apiServerPort: pick("fld-adv-api-port"),
      podSubnet: pick("fld-adv-pod-subnet"),
      serviceSubnet: pick("fld-adv-svc-subnet"),
      kubeProxyMode: pick("fld-adv-kube-proxy"),
      disableDefaultCNI: pickChk("fld-adv-disable-cni"),
      portEnabled: pickChk("fld-adv-port-enable"),
      hostPort: pick("fld-adv-host-port"),
      containerPort: pickNum("fld-adv-container-port", 80),
      portListenAddress: pick("fld-adv-listen-addr"),
      portProtocol: pick("fld-adv-port-proto") || "TCP",
      kubeadmPatch: (function () {
        const el = document.getElementById("fld-adv-kubeadm");
        return el ? el.value : "";
      })(),
    };
  }

  function advancedFormSnapshotString() {
    try {
      return JSON.stringify(readAdvancedFormFromDom());
    } catch (e) {
      return "";
    }
  }

  /**
   * @param {AdvancedFormState} f
   */
  function applyAdvancedFormToDom(f) {
    const set = function (id, v) {
      const el = document.getElementById(id);
      if (el) el.value = v != null ? String(v) : "";
    };
    ensureApiVersionOption(f.apiVersion || "kind.x-k8s.io/v1alpha4");
    set("fld-adv-api-version", f.apiVersion || "kind.x-k8s.io/v1alpha4");
    set("fld-adv-ip-family", f.ipFamily || "");
    set("fld-adv-api-addr", f.apiServerAddress || "");
    set("fld-adv-api-port", f.apiServerPort || "");
    set("fld-adv-pod-subnet", f.podSubnet || "");
    set("fld-adv-svc-subnet", f.serviceSubnet || "");
    set("fld-adv-kube-proxy", f.kubeProxyMode || "");
    const cni = document.getElementById("fld-adv-disable-cni");
    if (cni) cni.checked = !!f.disableDefaultCNI;
    set("fld-adv-image", f.image || "");
    set("fld-adv-cp", String(Math.min(5, Math.max(1, f.controlPlanes || 1))));
    set("fld-adv-workers", String(Math.min(20, Math.max(0, f.workers || 0))));
    const pe = document.getElementById("fld-adv-port-enable");
    if (pe) pe.checked = !!f.portEnabled;
    set("fld-adv-host-port", f.hostPort || "");
    set("fld-adv-container-port", String(f.containerPort || 80));
    set("fld-adv-listen-addr", f.portListenAddress || "");
    set("fld-adv-port-proto", f.portProtocol || "TCP");
    const kub = document.getElementById("fld-adv-kubeadm");
    if (kub) kub.value = f.kubeadmPatch || "";
    togglePortRow();
  }

  function togglePortRow() {
    const pe = document.getElementById("fld-adv-port-enable");
    const row = document.getElementById("adv-port-fields-row");
    const on = !!(pe && pe.checked);
    if (row) {
      row.classList.toggle("hidden", !on);
      row.setAttribute("aria-hidden", on ? "false" : "true");
    }
  }

  function updateAdvancedPreview() {
    const pre = document.getElementById("adv-yaml-preview");
    if (!pre) return;
    try {
      pre.textContent = serializeAdvancedKindCluster(readAdvancedFormFromDom());
    } catch (e) {
      pre.textContent = "# " + (e.message || "Заполните образ узла");
    }
  }

  function setModeUi(isAdvanced) {
    const simple = document.getElementById("edit-simple-block");
    const adv = document.getElementById("edit-advanced-block");
    const hint = document.getElementById("cluster-edit-mode-hint");
    const radios = document.querySelectorAll('input[name="save_mode"]');
    radios.forEach(function (r) {
      r.checked = (isAdvanced && r.value === "yaml") || (!isAdvanced && r.value === "simple");
    });
    if (simple) {
      simple.classList.toggle("hidden", isAdvanced);
      simple.setAttribute("aria-hidden", isAdvanced ? "true" : "false");
    }
    if (adv) {
      adv.classList.toggle("hidden", !isAdvanced);
      adv.setAttribute("aria-hidden", !isAdvanced ? "true" : "false");
    }
    if (hint) {
      hint.textContent = isAdvanced
        ? "Секции соответствуют kind Cluster: networking, nodes, extraPortMappings и kubeadmConfigPatches (к первой control-plane)."
        : "Только тег kindest/node и число worker-нод; конфиг пересобирается как при создании кластера.";
    }
    if (isAdvanced) updateAdvancedPreview();
  }

  function getSaveMode() {
    const c = document.querySelector('input[name="save_mode"]:checked');
    return c && c.value === "yaml" ? "yaml" : "simple";
  }

  function normTag(s) {
    return String(s || "")
      .trim()
      .toLowerCase()
      .replace(/^v/, "");
  }

  async function loadConfig() {
    const noteEl = document.getElementById("edit-kind-note");
    const msg = document.getElementById("edit-msg");
    const verInput = document.getElementById("kubernetes_version_edit");
    const workersEl = document.getElementById("fld-workers-edit");
    const descEl = document.getElementById("fld-description");
    try {
      const d = await api("/clusters/" + encodeURIComponent(clusterName) + "/config");
      if (noteEl) {
        noteEl.textContent = d.kind_note || "";
      }
      const meta = d.meta || {};
      initialTag = meta.kubernetes_version_tag != null ? String(meta.kubernetes_version_tag) : "";
      if (verInput) {
        const tag = initialTag;
        verInput.value = tag ? tag.replace(/^v/i, "") : "";
      }
      if (meta.worker_nodes != null) {
        const wn = Number(meta.worker_nodes);
        initialWorkers = Number.isNaN(wn) ? 0 : wn;
      } else {
        initialWorkers = null;
      }
      if (workersEl) {
        workersEl.value = String(initialWorkers !== null ? initialWorkers : 0);
      }
      initialDescription = meta.description != null ? String(meta.description) : "";
      if (descEl) {
        descEl.value = initialDescription;
      }
      const yamlText = d.kind_config_yaml != null ? String(d.kind_config_yaml) : "";
      loadedYamlSnapshot = yamlText;
      const advParsed = parseKindYamlToAdvancedForm(yamlText);
      if (!advParsed.image && meta.node_image) {
        advParsed.image = String(meta.node_image);
      }
      if (advParsed.controlPlanes < 1) advParsed.controlPlanes = 1;
      applyAdvancedFormToDom(advParsed);
      initialAdvancedSnapshot = advancedFormSnapshotString();
      updateAdvancedPreview();
      if (msg) msg.textContent = "";
    } catch (e) {
      if (noteEl) noteEl.textContent = "";
      if (msg) {
        msg.textContent = e.status === 404 ? "Каталог кластера не найден." : "Ошибка загрузки: " + e.message;
      }
    }
  }

  function bindAdvancedInputs() {
    const ids = [
      "fld-adv-api-version",
      "fld-adv-ip-family",
      "fld-adv-api-addr",
      "fld-adv-api-port",
      "fld-adv-pod-subnet",
      "fld-adv-svc-subnet",
      "fld-adv-kube-proxy",
      "fld-adv-disable-cni",
      "fld-adv-image",
      "fld-adv-cp",
      "fld-adv-workers",
      "fld-adv-port-enable",
      "fld-adv-host-port",
      "fld-adv-container-port",
      "fld-adv-listen-addr",
      "fld-adv-port-proto",
      "fld-adv-kubeadm",
    ];
    ids.forEach(function (id) {
      const el = document.getElementById(id);
      if (!el) return;
      el.addEventListener("input", updateAdvancedPreview);
      el.addEventListener("change", updateAdvancedPreview);
    });
    const pe = document.getElementById("fld-adv-port-enable");
    if (pe) {
      pe.addEventListener("change", function () {
        togglePortRow();
        updateAdvancedPreview();
      });
    }
  }

  function bindForm() {
    const form = document.getElementById("form-cluster-edit");
    if (!form) return;
    document.querySelectorAll('input[name="save_mode"]').forEach(function (r) {
      r.addEventListener("change", function () {
        setModeUi(getSaveMode() === "yaml");
      });
    });

    form.addEventListener("submit", async function (ev) {
      ev.preventDefault();
      const msg = document.getElementById("edit-msg");
      const mode = getSaveMode();
      const descEl = document.getElementById("fld-description");
      const descVal = descEl ? descEl.value : "";
      /** @type {Record<string, unknown>} */
      const payload = { description: descVal };

      if (mode === "yaml") {
        let yamlText;
        try {
          yamlText = serializeAdvancedKindCluster(readAdvancedFormFromDom());
        } catch (e) {
          if (msg) msg.textContent = e.message || "Проверьте поля расширенного режима.";
          return;
        }
        const yamlChanged = yamlText !== (loadedYamlSnapshot || "");
        const advChanged = advancedFormSnapshotString() !== initialAdvancedSnapshot;
        const descChanged = descVal !== initialDescription;
        if (!yamlChanged && !advChanged && !descChanged) {
          if (msg) msg.textContent = "Изменений нет.";
          return;
        }
        if (yamlChanged || advChanged) {
          payload.kind_config_yaml = yamlText;
        }
      } else {
        const verInput = document.getElementById("kubernetes_version_edit");
        const workersEl = document.getElementById("fld-workers-edit");
        const ver = verInput ? verInput.value.trim() : "";
        const wParsed = workersEl ? parseInt(workersEl.value, 10) : NaN;
        const workersOk = !Number.isNaN(wParsed);
        const effInitW = initialWorkers !== null && initialWorkers !== undefined ? initialWorkers : 0;
        const workersChanged = workersOk && wParsed !== effInitW;
        const verChanged = normTag(ver) !== normTag(initialTag);
        const descChanged = descVal !== initialDescription;
        if (!verChanged && !workersChanged && !descChanged) {
          if (msg) msg.textContent = "Изменений нет.";
          return;
        }
        if (verChanged) {
          if (!ver) {
            if (msg) msg.textContent = "Укажите тег образа или откатите поле к прежнему значению.";
            return;
          }
          payload.kubernetes_version = ver;
        }
        if (workersChanged) {
          if (!workersOk || wParsed < 0 || wParsed > 20) {
            if (msg) msg.textContent = "Число worker-нод должно быть от 0 до 20.";
            return;
          }
          payload.workers = wParsed;
        }
      }

      if (msg) msg.textContent = "Сохранение…";
      try {
        const r = await api("/clusters/" + encodeURIComponent(clusterName) + "/config", {
          method: "PUT",
          headers: { "Content-Type": "application/json", Accept: "application/json" },
          body: JSON.stringify(payload),
        });
        if (msg) {
          msg.textContent = r.message || "Сохранено.";
        }
        openClusterEditSaveModal(!!r.updated_kind_config_yaml, !!r.registered_in_kind);
        await loadConfig();
      } catch (e) {
        if (msg) msg.textContent = "Ошибка: " + e.message;
      }
    });
  }

  wireSaveInfoModal();

  loadVersions();
  loadConfig().then(function () {
    bindAdvancedInputs();
    togglePortRow();
  });
  bindForm();
  setModeUi(false);
})();
