
(() => {
  "use strict";
  const $ = (s, r = document) => r.querySelector(s);
  const clean = (v) => String(v ?? "").trim();
  const svg = (name, attrs = {}) => {
    const node = document.createElementNS("http://www.w3.org/2000/svg", name);
    Object.entries(attrs).forEach(([key, value]) => node.setAttribute(key, String(value)));
    return node;
  };
  const list = $("#entity-list"), detail = $("#entity-detail"), map = $("#entity-map");
  if (!list || !detail || !map) return;
  let graph = null, filtered = [], selected = null;
  const normalized = (v) => clean(v).toLocaleLowerCase("ru");
  const typeLabels = { человек: "человек", псевдоним: "псевдоним", институция: "институция", сайт: "сайт", метод: "метод", понятие: "понятие", книга: "книга", исследование: "исследование", производная: "производная", "набор данных": "набор данных", "модуль памяти": "модуль памяти", источник: "источник" };
  const nodeById = (id) => graph?.узлы?.find((node) => node.идентификатор === id);
  const relations = (id) => (graph?.связи || []).filter((edge) => edge.от === id || edge.к === id || edge.источник === id || edge.цель === id);
  const edgeEnds = (edge) => [edge.от ?? edge.источник, edge.к ?? edge.цель];
  const edgeLabel = (edge) => edge.связь ?? edge.отношение ?? edge.тип ?? "связан";
  const nodePath = (node) => node.путь || node.файл || "";
  const renderMap = (node) => {
    map.replaceChildren();
    const edges = relations(node.идентификатор).slice(0, 16);
    const neighbors = edges.map((edge) => {
      const [a, b] = edgeEnds(edge); return nodeById(a === node.идентификатор ? b : a);
    }).filter(Boolean);
    const centerX = 400, centerY = 260, radius = 190;
    const defs = svg("defs");
    const filter = svg("filter", { id: "entity-glow", x: "-50%", y: "-50%", width: "200%", height: "200%" });
    const blur = svg("feGaussianBlur", { stdDeviation: "6", result: "blur" });
    const merge = svg("feMerge"); merge.append(svg("feMergeNode", { in: "blur" }), svg("feMergeNode", { in: "SourceGraphic" })); filter.append(blur, merge); defs.append(filter); map.append(defs);
    neighbors.forEach((neighbor, index) => {
      const angle = (Math.PI * 2 * index / Math.max(1, neighbors.length)) - Math.PI / 2;
      const x = centerX + Math.cos(angle) * radius, y = centerY + Math.sin(angle) * radius;
      map.append(svg("line", { x1: centerX, y1: centerY, x2: x, y2: y, class: "entity-edge" }));
      const group = svg("g", { class: "entity-node neighbor", tabindex: "0", role: "button", "aria-label": neighbor.название, "data-id": neighbor.идентификатор });
      group.append(svg("circle", { cx: x, cy: y, r: 35 }));
      const text = svg("text", { x, y: y + 55, "text-anchor": "middle" }); text.textContent = neighbor.название.length > 23 ? `${neighbor.название.slice(0, 21)}…` : neighbor.название; group.append(text); map.append(group);
    });
    const center = svg("g", { class: "entity-node center", filter: "url(#entity-glow)" }); center.append(svg("circle", { cx: centerX, cy: centerY, r: 66 }));
    const label = svg("text", { x: centerX, y: centerY - 6, "text-anchor": "middle" }); label.textContent = node.название.length > 22 ? `${node.название.slice(0, 20)}…` : node.название;
    const type = svg("text", { x: centerX, y: centerY + 20, "text-anchor": "middle", class: "entity-type" }); type.textContent = typeLabels[node.вид] || node.вид;
    center.append(label, type); map.append(center);
    map.querySelectorAll("[data-id]").forEach((group) => {
      const activate = () => select(group.dataset.id);
      group.addEventListener("click", activate); group.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); activate(); } });
    });
  };
  const select = (id) => {
    selected = nodeById(id); if (!selected) return;
    list.querySelectorAll("button").forEach((button) => button.setAttribute("aria-current", button.dataset.id === id ? "true" : "false"));
    detail.replaceChildren();
    const code = document.createElement("span"); code.className = "case-code"; code.textContent = selected.идентификатор;
    const h = document.createElement("h2"); h.textContent = selected.название;
    const type = document.createElement("p"); type.className = "entity-kind"; type.textContent = typeLabels[selected.вид] || selected.вид;
    const dl = document.createElement("dl");
    Object.entries(selected).filter(([key]) => !["идентификатор", "название", "вид"].includes(key)).forEach(([key, value]) => {
      const wrap = document.createElement("div"), dt = document.createElement("dt"), dd = document.createElement("dd"); dt.textContent = key.replaceAll("_", " ");
      if ((key === "путь" || key === "файл") && clean(value)) { const a = document.createElement("a"); a.href = value; a.textContent = value; dd.appendChild(a); } else dd.textContent = Array.isArray(value) ? value.join(", ") : String(value);
      wrap.append(dt, dd); dl.appendChild(wrap);
    });
    const rh = document.createElement("h3"); rh.textContent = "Связи";
    const ul = document.createElement("ul"); ul.className = "relation-list";
    relations(id).forEach((edge) => {
      const [a, b] = edgeEnds(edge), inbound = b === id, other = nodeById(inbound ? a : b), li = document.createElement("li"), button = document.createElement("button"); button.type = "button"; button.className = "relation-button";
      const direction = inbound ? "←" : "→"; button.textContent = `${direction} ${edgeLabel(edge)} · ${other?.название || (inbound ? a : b)}`; button.addEventListener("click", () => other && select(other.идентификатор)); li.appendChild(button); ul.appendChild(li);
    });
    if (!ul.children.length) { const li = document.createElement("li"); li.textContent = "Связи не записаны."; ul.appendChild(li); }
    const actions = document.createElement("div"); actions.className = "actions";
    const path = nodePath(selected); if (path) { const a = document.createElement("a"); a.className = "button primary"; a.href = path; a.textContent = "Открыть сущность"; actions.appendChild(a); }
    detail.append(code, h, type, dl, rh, ul, actions); renderMap(selected);
  };
  const renderList = () => {
    const query = normalized($("#entity-search")?.value), kind = $("#entity-kind")?.value || "все";
    filtered = (graph.узлы || []).filter((node) => (kind === "все" || node.вид === kind) && (!query || normalized(`${node.название} ${node.идентификатор} ${node.вид}`).includes(query)));
    list.replaceChildren();
    filtered.forEach((node) => { const button = document.createElement("button"); button.type = "button"; button.className = "entity-button"; button.dataset.id = node.идентификатор; button.innerHTML = `<span>${typeLabels[node.вид] || node.вид}</span><strong></strong>`; button.querySelector("strong").textContent = node.название; button.addEventListener("click", () => select(node.идентификатор)); list.appendChild(button); });
    $("#entity-count").textContent = `Показано: ${filtered.length} из ${graph.узлы.length}. Связей: ${graph.связи.length}.`;
    if (!filtered.length) list.innerHTML = '<p class="empty-state is-visible">Совпадений нет.</p>';
    if (!selected && filtered[0]) select(filtered[0].идентификатор);
  };
  fetch("сущности/граф-связей.json", { cache: "no-store" }).then((response) => { if (!response.ok) throw new Error(); return response.json(); }).then((data) => {
    let local=[];
    try { local=JSON.parse(localStorage.getItem('война-реестр-требований-4-3') || '[]'); if(!Array.isArray(local))local=[]; } catch { local=[]; }
    graph = globalThis.ВойнаМетодика.requirementGraph(local, data);
    const selectNode = $("#entity-kind"); [...new Set(graph.узлы.map((node) => node.вид))].sort((a, b) => a.localeCompare(b, "ru")).forEach((kind) => { const option = document.createElement("option"); option.value = kind; option.textContent = typeLabels[kind] || kind; selectNode.appendChild(option); });
    $("#entity-search")?.addEventListener("input", renderList); selectNode?.addEventListener("change", () => { selected = null; renderList(); }); renderList();
  }).catch(() => { list.innerHTML = '<p class="empty-state is-visible">Граф не удалось прочитать. Проверьте файл сущности/граф-связей.json.</p>'; $("#entity-count").textContent = "Граф недоступен."; });
})();
