(() => {
  "use strict";

  const $ = (selector, root = document) => root.querySelector(selector);
  const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));
  const normalize = (value) => String(value || "").trim().toLocaleLowerCase("ru");

  const saveText = (filename, text) => {
    const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(url);
  };

  // Сравнительный атлас.
  const atlasCards = $$('[data-atlas-card]');
  const atlasSearch = $("#atlas-search");
  const atlasButtons = $$(".atlas-layer-button");
  let atlasLayer = "все";
  const applyAtlas = () => {
    if (!atlasCards.length) return;
    const query = normalize(atlasSearch?.value);
    let visible = 0;
    atlasCards.forEach((card) => {
      const matchLayer = atlasLayer === "все" || card.dataset.atlasLead === atlasLayer;
      const matchQuery = !query || normalize(card.dataset.atlasSearch || card.textContent).includes(query);
      const show = matchLayer && matchQuery;
      card.hidden = !show;
      if (show) visible += 1;
    });
    const count = $("#atlas-count");
    if (count) count.textContent = String(visible);
    $("#atlas-empty")?.classList.toggle("is-visible", visible === 0);
  };
  atlasSearch?.addEventListener("input", applyAtlas);
  atlasButtons.forEach((button) => button.addEventListener("click", () => {
    atlasLayer = button.dataset.atlasLayer || "все";
    atlasButtons.forEach((item) => {
      const active = item === button;
      item.classList.toggle("is-active", active);
      item.setAttribute("aria-pressed", String(active));
    });
    applyAtlas();
  }));
  applyAtlas();

  // Карта происхождения.
  const provenanceCards = $$('[data-provenance-card]');
  const provenanceSearch = $("#provenance-search");
  const applyProvenance = () => {
    if (!provenanceCards.length) return;
    const query = normalize(provenanceSearch?.value);
    let visible = 0;
    provenanceCards.forEach((card) => {
      const show = !query || normalize(card.dataset.provenanceSearch || card.textContent).includes(query);
      card.hidden = !show;
      if (show) visible += 1;
    });
    const count = $("#provenance-count");
    if (count) count.textContent = String(visible);
    $("#provenance-empty")?.classList.toggle("is-visible", visible === 0);
  };
  provenanceSearch?.addEventListener("input", applyProvenance);
  applyProvenance();

  // Парный тест: различие измеряется, но не объявляется причиной.
  const pairForm = $("#paired-test");
  const pairOutput = $("#pair-output");
  const pairResults = $("#pair-results");
  const pairStatus = $("#pair-status");
  const laneNames = {
    чистый: "Чистый сеанс",
    профиль: "Зарегистрированный профиль",
    регион: "Другой регион",
    возраст: "Возрастной режим"
  };

  const tokenSet = (text) => new Set((normalize(text).match(/[\p{L}\p{N}]+/gu) || []).filter((x) => x.length > 1));
  const jaccard = (a, b) => {
    const aa = tokenSet(a);
    const bb = tokenSet(b);
    const union = new Set([...aa, ...bb]);
    if (!union.size) return 1;
    let common = 0;
    aa.forEach((token) => { if (bb.has(token)) common += 1; });
    return common / union.size;
  };
  const lineSet = (text) => new Set(String(text || "").split(/\r?\n/).map(normalize).filter(Boolean));
  const sourceOverlap = (a, b) => {
    const aa = lineSet(a);
    const bb = lineSet(b);
    const union = new Set([...aa, ...bb]);
    if (!union.size) return 1;
    let common = 0;
    aa.forEach((item) => { if (bb.has(item)) common += 1; });
    return common / union.size;
  };
  const readLanes = () => $$("[data-pair-lane]", pairForm).map((lane) => ({
    key: lane.dataset.pairLane,
    name: laneNames[lane.dataset.pairLane] || lane.dataset.pairLane,
    version: $("[data-pair-version]", lane)?.value.trim() || "не указана",
    time: $("[data-pair-time]", lane)?.value.trim() || "не указано",
    response: $("[data-pair-response]", lane)?.value.trim() || "",
    sources: $("[data-pair-sources]", lane)?.value.trim() || "",
    refusal: Boolean($("[data-pair-refusal]", lane)?.checked)
  }));

  let currentPairReport = "";
  const renderPair = () => {
    if (!pairForm || !pairResults || !pairOutput) return "";
    const query = $("#pair-query")?.value.trim() || "";
    const subject = $("#pair-subject")?.value.trim() || "не указан";
    const lanes = readLanes();
    const baseline = lanes.find((lane) => lane.key === "чистый");
    const completed = lanes.filter((lane) => lane.response || lane.sources || lane.refusal);
    pairResults.replaceChildren();

    if (!query || !baseline || !baseline.response || completed.length < 2) {
      const p = document.createElement("p");
      p.className = "empty-state is-visible";
      p.textContent = "Нужны неизменяемый запрос, полный ответ чистого сеанса и данные хотя бы одного второго условия.";
      pairResults.appendChild(p);
      if (pairStatus) pairStatus.textContent = "Заполните данные хотя бы двух условий.";
      pairOutput.value = "";
      currentPairReport = "";
      return "";
    }

    const lines = [
      "ПАРНЫЙ ТЕСТ ПЕРСОНАЛИЗИРОВАННОЙ ЦЕНЗУРЫ",
      "",
      `Предмет: ${subject}`,
      "Запрос:",
      query,
      "",
      "БАЗОВАЯ ЛИНИЯ",
      `Условие: ${baseline.name}`,
      `Версия: ${baseline.version}`,
      `Время и условия: ${baseline.time}`,
      `Отказ: ${baseline.refusal ? "да" : "нет"}`,
      `Длина ответа: ${baseline.response.length}`,
      `Число источников: ${lineSet(baseline.sources).size}`,
      "",
      "СРАВНЕНИЕ"
    ];

    completed.filter((lane) => lane.key !== "чистый").forEach((lane) => {
      const similarity = jaccard(baseline.response, lane.response);
      const sourceSimilarity = sourceOverlap(baseline.sources, lane.sources);
      const lengthDelta = baseline.response.length
        ? ((lane.response.length - baseline.response.length) / baseline.response.length) * 100
        : 0;
      const refusalMismatch = baseline.refusal !== lane.refusal;
      const card = document.createElement("article");
      card.className = "pair-result-card";
      const h3 = document.createElement("h3");
      h3.textContent = lane.name;
      card.appendChild(h3);
      const dl = document.createElement("dl");
      const values = [
        ["Сходство текста", `${Math.round(similarity * 100)}%`],
        ["Изменение длины", `${lengthDelta >= 0 ? "+" : ""}${lengthDelta.toFixed(1)}%`],
        ["Сходство источников", `${Math.round(sourceSimilarity * 100)}%`],
        ["Различие отказа", refusalMismatch ? "да" : "нет"]
      ];
      values.forEach(([label, value]) => {
        const dt = document.createElement("dt"); dt.textContent = label;
        const dd = document.createElement("dd"); dd.textContent = value;
        dl.append(dt, dd);
      });
      card.appendChild(dl);
      pairResults.appendChild(card);
      lines.push(
        "",
        lane.name.toUpperCase(),
        `Версия: ${lane.version}`,
        `Время и условия: ${lane.time}`,
        `Отказ: ${lane.refusal ? "да" : "нет"}`,
        `Сходство текста: ${Math.round(similarity * 100)}%`,
        `Изменение длины: ${lengthDelta >= 0 ? "+" : ""}${lengthDelta.toFixed(1)}%`,
        `Сходство источников: ${Math.round(sourceSimilarity * 100)}%`,
        `Различие отказа: ${refusalMismatch ? "да" : "нет"}`
      );
    });

    lines.push(
      "",
      "ПРЕДЕЛ ВЫВОДА",
      "Различие не доказывает персонализированную цензуру. Возможны версия модели, случайность генерации, изменение индекса, время, язык, эксперимент интерфейса, региональная доступность и ошибка фиксации.",
      "Для повышения уверенности повторите неизменяемый запрос несколько раз, поменяйте только одну переменную, сохраните снимки, источники, версию и точное время.",
      "",
      "война.com · РЕДАКЦИЯ 4.2.0 · РЕБЕЛ ОР ДАЙ"
    );
    currentPairReport = lines.join("\n");
    pairOutput.value = currentPairReport;
    if (pairStatus) pairStatus.textContent = `Сравнено условий: ${completed.length}. Причинный вывод не присваивается автоматически.`;
    return currentPairReport;
  };

  pairForm?.addEventListener("submit", (event) => { event.preventDefault(); renderPair(); pairOutput?.focus(); });
  pairForm?.addEventListener("reset", () => window.setTimeout(() => {
    pairResults?.replaceChildren();
    if (pairResults) {
      const p = document.createElement("p"); p.className = "empty-state is-visible"; p.textContent = "Результат появится после расчёта."; pairResults.appendChild(p);
    }
    if (pairOutput) pairOutput.value = "";
    if (pairStatus) pairStatus.textContent = "Заполните данные хотя бы двух условий.";
    currentPairReport = "";
  }, 0));
  $("#pair-download")?.addEventListener("click", () => {
    const report = currentPairReport || renderPair();
    if (report) saveText("парный-тест-персонализированной-цензуры.txt", report);
  });

  // Журнал восстановительных учений.
  const drillGrid = $("#drill-grid");
  if (drillGrid) {
    fetch("журнал-учений.json", { cache: "no-store" }).then((response) => {
      if (!response.ok) throw new Error("журнал недоступен");
      return response.json();
    }).then((data) => {
      drillGrid.replaceChildren();
      const drills = data.учения || [];
      drills.forEach((drill) => {
        const article = document.createElement("article");
        article.className = "drill-card";
        article.dataset.state = drill.состояние || "неизвестно";
        const header = document.createElement("header");
        const code = document.createElement("span"); code.className = "case-code"; code.textContent = String(drill.номер).padStart(2, "0");
        const state = document.createElement("span"); state.className = "state"; state.textContent = drill.состояние || "неизвестно";
        header.append(code, state);
        const h2 = document.createElement("h2"); h2.textContent = drill.сценарий;
        const p = document.createElement("p"); p.textContent = drill.задача;
        const dl = document.createElement("dl");
        [
          ["Факт", drill.факт || "контроль ещё не записан"],
          ["Доказательство", drill.доказательство || "отсутствует"],
          ["Ограничение", drill.ограничение || "будет определено после запуска"]
        ].forEach(([label, value]) => {
          const wrap = document.createElement("div");
          const dt = document.createElement("dt"); dt.textContent = label;
          const dd = document.createElement("dd"); dd.textContent = value;
          wrap.append(dt, dd); dl.appendChild(wrap);
        });
        article.append(header, h2, p, dl);
        drillGrid.appendChild(article);
      });
      const passed = drills.filter((x) => x.состояние === "пройдено").length;
      const summary = $("#drill-summary");
      if (summary) summary.textContent = `${passed} из ${drills.length} учений подтверждены сохранённым доказательством.`;
    }).catch(() => {
      drillGrid.textContent = "Журнал учений не удалось прочитать. Проверьте сетевой путь и целостность файла.";
      const summary = $("#drill-summary");
      if (summary) summary.textContent = "Журнал недоступен.";
    });
  }

  // Библиографический индекс: ограниченная отрисовка защищает страницу от перегрузки.
  const bibliographyList = $("#bibliography-list");
  const bibliographySearch = $("#bibliography-search");
  const bibliographyRole = $("#bibliography-role");
  let bibliographyEntries = [];
  const renderBibliography = () => {
    if (!bibliographyList) return;
    const query = normalize(bibliographySearch?.value);
    const role = bibliographyRole?.value || "все";
    const matches = bibliographyEntries.filter((entry) => {
      const roleMatch = role === "все" || entry.роль === role;
      const haystack = normalize(`${entry.адрес} ${entry.домен} ${entry.роль} ${(entry.доклады || []).join(" ")}`);
      return roleMatch && (!query || haystack.includes(query));
    });
    bibliographyList.replaceChildren();
    matches.slice(0, 120).forEach((entry) => {
      const article = document.createElement("article"); article.className = "bibliography-item";
      const number = document.createElement("span"); number.className = "bib-number"; number.textContent = String(entry.номер).padStart(4, "0");
      const link = document.createElement("a"); link.href = entry.адрес; link.target = "_blank"; link.rel = "noreferrer nofollow"; link.textContent = entry.адрес;
      const meta = document.createElement("div"); meta.className = "bib-meta";
      const domain = document.createElement("strong"); domain.textContent = entry.домен;
      const roleText = document.createElement("span"); roleText.textContent = entry.роль;
      const reports = document.createElement("span"); reports.textContent = `Доклады: ${(entry.доклады || []).join(", ")}`;
      meta.append(domain, roleText, reports);
      article.append(number, link, meta);
      bibliographyList.appendChild(article);
    });
    const count = $("#bibliography-count");
    if (count) count.textContent = `Совпадений: ${matches.length}. В индексе: ${bibliographyEntries.length}.`;
    const limit = $("#bibliography-limit");
    if (limit) limit.hidden = matches.length <= 120;
    if (!matches.length) {
      const p = document.createElement("p"); p.className = "empty-state is-visible"; p.textContent = "Совпадений нет."; bibliographyList.appendChild(p);
    }
  };
  if (bibliographyList) {
    fetch("библиография.json", { cache: "no-store" }).then((response) => {
      if (!response.ok) throw new Error("индекс недоступен");
      return response.json();
    }).then((data) => {
      bibliographyEntries = data.источники || [];
      renderBibliography();
    }).catch(() => {
      bibliographyList.textContent = "Библиографический индекс не удалось прочитать. Проверьте локальный файл библиография.json.";
      const count = $("#bibliography-count"); if (count) count.textContent = "Индекс недоступен.";
    });
    bibliographySearch?.addEventListener("input", renderBibliography);
    bibliographyRole?.addEventListener("change", renderBibliography);
  }
})();
