/**
 * Страница «Журнал»: режимы per_cluster / provision / helm_addons, пагинация по 30.
 *
 * Автор: Сергей Антропов
 * Сайт: https://devops.org.ru
 */
(function () {
  "use strict";

  const body = document.body;
  if ((body.getAttribute("data-dashboard-mode") || "") !== "journal") return;

  const API = (body.dataset.apiBase || "/api/v1").replace(/\/$/, "");
  var PAGE_SIZE = 30;

  /** @type {'per_cluster' | 'provision' | 'helm_addons'} */
  var viewMode = "per_cluster";
  var currentPage = 1;
  /** Выбранный кластер в режиме per_cluster */
  var selectedCluster = "";

  async function apiGet(path) {
    const url = path.startsWith("http") ? path : API + path;
    const r = await fetch(url, { headers: { Accept: "application/json" } });
    const text = await r.text();
    var data;
    try {
      data = text ? JSON.parse(text) : null;
    } catch (e) {
      data = { raw: text };
    }
    if (!r.ok) {
      const msg =
        typeof data.detail === "string"
          ? data.detail
          : Array.isArray(data.detail)
            ? data.detail.map(function (x) { return x.msg || JSON.stringify(x); }).join("; ")
            : text || r.statusText;
      const err = new Error(msg);
      err.status = r.status;
      throw err;
    }
    return data;
  }

  const tbody = document.getElementById("journal-tbody");
  const statusEl = document.getElementById("journal-status");
  const emptyEl = document.getElementById("journal-empty");
  const tableWrap = document.getElementById("journal-table-wrap");
  const refreshBtn = document.getElementById("journal-refresh-btn");
  const pagNav = document.getElementById("journal-pagination");
  const clusterSelect = document.getElementById("journal-cluster-select");
  const clusterPicker = document.getElementById("journal-cluster-picker");
  const thKind = document.getElementById("journal-th-kind");
  const sectionTitle = document.getElementById("journal-table-section-title");

  function hideJournalPageLoadingOverlay() {
    const el = document.getElementById("journal-page-loading-overlay");
    if (!el) return;
    el.classList.add("hidden");
    el.setAttribute("aria-busy", "false");
    el.setAttribute("aria-hidden", "true");
    document.body.classList.remove("journal-page-loading");
  }

  function esc(s) {
    const d = document.createElement("div");
    d.textContent = s == null ? "" : String(s);
    return d.innerHTML;
  }

  function formatUtc(iso) {
    if (!iso) return "—";
    try {
      const d = new Date(iso);
      if (Number.isNaN(d.getTime())) return iso;
      return d.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, " Z");
    } catch (e) {
      return iso;
    }
  }

  /**
   * Ячейка «время»: дата и время отдельными span (при ширине до 920px — в две строки, см. CSS .journal-time-stack).
   * @param {string} iso
   * @returns {string} безопасный HTML
   */
  function formatUtcCellHtml(iso) {
    if (!iso) {
      return esc("—");
    }
    try {
      const d = new Date(iso);
      if (Number.isNaN(d.getTime())) {
        return esc(iso);
      }
      const day = d.toISOString().slice(0, 10);
      const clock = d.toISOString().slice(11, 19);
      return (
        '<span class="journal-time-stack">' +
        '<span class="journal-time-date">' +
        esc(day) +
        "</span>" +
        '<span class="journal-time-clock">' +
        esc(clock) +
        "</span>" +
        "</span>"
      );
    } catch (e) {
      return esc(iso);
    }
  }

  function statusClass(st) {
    const s = (st || "").toLowerCase();
    if (s === "success") return "journal-status-pill journal-status-pill--ok";
    if (s === "failed") return "journal-status-pill journal-status-pill--err";
    if (s === "cancelled") return "journal-status-pill journal-status-pill--warn";
    return "journal-status-pill";
  }

  /** Суффикс job_kind (после helm_addon_install_|upgrade_|delete_) → id аддона в API. */
  function helmJobSlugToAddonId(slug) {
    const m = {
      ingress_nginx: "ingress-nginx",
      kube_prometheus_stack: "kube-prometheus-stack",
      metrics_server: "metrics-server",
      istio_kiali: "istio-kiali",
    };
    return m[slug] || String(slug || "").replace(/_/g, "-") || "—";
  }

  function helmAddonTitle(id) {
    const m = {
      "ingress-nginx": "ingress-nginx",
      "kube-prometheus-stack": "kube-prometheus-stack",
      "metrics-server": "metrics-server",
      "istio-kiali": "Istio + Kiali",
    };
    return m[id] || id || "—";
  }

  function helmOperationTitle(op) {
    const o = (op || "").toLowerCase();
    if (o === "install") return "Установка";
    if (o === "upgrade") return "Обновление";
    if (o === "delete") return "Удаление";
    return op || "—";
  }

  /**
   * Колонка «Вид операции» для режима Helm: установка / обновление / удаление и имя аддона.
   * Старые записи без result.action разбираются по префиксу kind.
   */
  function formatHelmJournalKind(e) {
    const r = e.result;
    if (r && typeof r === "object") {
      const verb = helmOperationTitle(r.action);
      const ad = r.addon != null ? helmAddonTitle(String(r.addon)) : "—";
      if (r.action && r.addon != null && String(r.addon).length) {
        return verb + " · " + ad;
      }
    }
    const k = e.kind || "";
    if (k.indexOf("helm_addon_delete_") === 0) {
      return "Удаление · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_delete_/, "")));
    }
    if (k.indexOf("helm_addon_upgrade_") === 0) {
      return "Обновление · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_upgrade_/, "")));
    }
    if (k.indexOf("helm_addon_install_") === 0) {
      return "Установка · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_install_/, "")));
    }
    return k || "—";
  }

  function visiblePageItems(current, total) {
    const delta = 2;
    const range = [];
    let i;
    for (i = 1; i <= total; i++) {
      if (i === 1 || i === total || (i >= current - delta && i <= current + delta)) {
        range.push(i);
      }
    }
    const out = [];
    let l = 0;
    for (i = 0; i < range.length; i++) {
      const p = range[i];
      if (l) {
        if (p - l === 2) out.push(l + 1);
        else if (p - l > 2) out.push("…");
      }
      out.push(p);
      l = p;
    }
    return out;
  }

  function renderPagination(total, limit, offset, page, totalPages, itemCount, onSelectPage) {
    if (!pagNav) return;
    pagNav.innerHTML = "";
    if (total <= 0 || totalPages <= 0) {
      pagNav.classList.add("hidden");
      return;
    }
    pagNav.classList.remove("hidden");

    const wrap = document.createElement("div");
    wrap.className = "journal-pagination-inner";

    function mkBtn(label, disabled, ariaLabel, onClick) {
      const b = document.createElement("button");
      b.type = "button";
      b.className = "journal-pag-btn";
      b.textContent = label;
      b.setAttribute("aria-label", ariaLabel);
      b.disabled = !!disabled;
      if (!disabled && onClick) b.addEventListener("click", onClick);
      return b;
    }

    /* Короткий текст на кнопках — одна строка пагинации; смысл в aria-label */
    wrap.appendChild(
      mkBtn("««", page <= 1, "Первая страница", function () { onSelectPage(1); }),
    );
    wrap.appendChild(
      mkBtn("‹", page <= 1, "Предыдущая страница", function () { onSelectPage(page - 1); }),
    );

    const pagesEl = document.createElement("div");
    pagesEl.className = "journal-pag-pages";
    pagesEl.setAttribute("role", "group");
    pagesEl.setAttribute("aria-label", "Номера страниц");

    visiblePageItems(page, totalPages).forEach(function (item) {
      if (item === "…") {
        const span = document.createElement("span");
        span.className = "journal-pag-ellipsis";
        span.setAttribute("aria-hidden", "true");
        span.textContent = "…";
        pagesEl.appendChild(span);
        return;
      }
      const pNum = item;
      const b = document.createElement("button");
      b.type = "button";
      b.className = "journal-pag-num" + (pNum === page ? " journal-pag-num--active" : "");
      b.textContent = String(pNum);
      b.setAttribute("aria-label", "Страница " + pNum);
      if (pNum === page) b.setAttribute("aria-current", "page");
      else b.removeAttribute("aria-current");
      if (pNum !== page) {
        b.addEventListener("click", function () { onSelectPage(pNum); });
      }
      pagesEl.appendChild(b);
    });

    wrap.appendChild(pagesEl);

    wrap.appendChild(
      mkBtn("›", page >= totalPages, "Следующая страница", function () { onSelectPage(page + 1); }),
    );
    wrap.appendChild(
      mkBtn("»»", page >= totalPages, "Последняя страница", function () { onSelectPage(totalPages); }),
    );

    pagNav.appendChild(wrap);
  }

  function goToPage(p) {
    if (p < 1) p = 1;
    currentPage = p;
    loadJournal(false);
  }

  /**
   * Построить строки таблицы: jobs (log_lines) или dir-лог (lines).
   */
  function renderTableRows(entries, mode) {
    if (!tbody) return;
    tbody.innerHTML = "";
    entries.forEach(function (e) {
      const tr = document.createElement("tr");
      tr.className = "journal-data-row";
      const cluster = e.source_cluster || e.cluster_name || "—";
      const finishedHtml = formatUtcCellHtml(
        e.finished_at_utc || e.created_at_utc,
      );
      const kind = mode === "helm_addons" ? formatHelmJournalKind(e) : (e.kind || "");
      const msg = (e.message || "").trim();
      let lines = [];
      if (mode === "per_cluster") {
        lines = Array.isArray(e.log_lines) ? e.log_lines : [];
      } else {
        lines = Array.isArray(e.lines) ? e.lines : [];
      }
      const logText = lines.join("\n");
      const detailsId = "journal-log-" + Math.random().toString(36).slice(2, 11);

      /* Классы create-jobs-card-td* — карточки на узком экране (<620px), см. .journal-list-card в style.css */
      tr.innerHTML =
        "<td class=\"journal-cell-time create-jobs-card-td create-jobs-card-td--time\">" +
        finishedHtml +
        "</td>" +
        "<td class=\"create-jobs-card-td create-jobs-card-td--cluster\"><code class=\"journal-code\">" +
        esc(cluster) +
        "</code></td>" +
        "<td class=\"create-jobs-card-td create-jobs-card-td--kind\"><code class=\"journal-code journal-cell-kind\">" +
        esc(kind) +
        "</code></td>" +
        "<td class=\"create-jobs-card-td create-jobs-card-td--status\"><span class=\"" +
        esc(statusClass(e.status)) +
        "\">" +
        esc(e.status || "") +
        "</span></td>" +
        "<td class=\"journal-cell-msg create-jobs-card-td create-jobs-card-td--msg\"><span class=\"create-jobs-msg-value\">" +
        esc(msg || "—") +
        "</span></td>" +
        "<td class=\"journal-cell-actions create-jobs-card-td create-jobs-card-td--log\">" +
        "<button type=\"button\" class=\"btn-secondary btn-journal-toggle\" aria-expanded=\"false\" aria-controls=\"" +
        detailsId +
        "\">Показать лог</button>" +
        "</td>";
      tbody.appendChild(tr);

      if (logText) {
        const trDetails = document.createElement("tr");
        trDetails.className = "journal-details-row hidden";
        trDetails.innerHTML =
          "<td colspan=\"6\" class=\"journal-details-cell\">" +
          "<pre id=\"" + detailsId + "\" class=\"mono journal-log-pre\" hidden></pre>" +
          "</td>";
        tbody.appendChild(trDetails);
        const pre = trDetails.querySelector("pre");
        if (pre) pre.textContent = logText;
        const btn = tr.querySelector(".btn-journal-toggle");
        if (btn && trDetails && pre) {
          btn.addEventListener("click", function () {
            const open = trDetails.classList.contains("hidden");
            trDetails.classList.toggle("hidden", !open);
            pre.hidden = !open;
            btn.setAttribute("aria-expanded", open ? "true" : "false");
            btn.textContent = open ? "Скрыть лог" : "Показать лог";
          });
        }
      } else {
        const btn = tr.querySelector(".btn-journal-toggle");
        if (btn) {
          btn.disabled = true;
          btn.textContent = "Нет строк";
        }
      }
    });
  }

  function applyViewLabels() {
    if (thKind) {
      if (viewMode === "per_cluster") thKind.textContent = "Тип задания";
      else thKind.textContent = "Вид операции";
    }
    if (sectionTitle) {
      if (viewMode === "per_cluster") sectionTitle.textContent = "Задания выбранного кластера";
      else if (viewMode === "provision") sectionTitle.textContent = "Развёртывание по кластерам";
      else sectionTitle.textContent = "Helm-аддоны по кластерам";
    }
    if (clusterPicker) {
      clusterPicker.classList.toggle("hidden", viewMode !== "per_cluster");
    }
  }

  function buildListUrl() {
    const off = (currentPage - 1) * PAGE_SIZE;
    const q =
      "?limit=" + encodeURIComponent(String(PAGE_SIZE)) +
      "&offset=" + encodeURIComponent(String(off));
    if (viewMode === "per_cluster") {
      return "/journal/recent" + q + "&cluster=" + encodeURIComponent(selectedCluster);
    }
    if (viewMode === "provision") return "/journal/provision" + q;
    return "/journal/helm-addons" + q;
  }

  async function loadJournal(resetPage) {
    if (!tbody || !statusEl) {
      hideJournalPageLoadingOverlay();
      return;
    }
    applyViewLabels();

    if (resetPage) currentPage = 1;

    if (viewMode === "per_cluster" && !selectedCluster) {
      statusEl.textContent = "Выберите кластер.";
      tbody.innerHTML = "";
      if (emptyEl) {
        emptyEl.classList.remove("hidden");
        emptyEl.textContent = "Укажите кластер в списке выше.";
      }
      if (tableWrap) tableWrap.classList.add("hidden");
      if (pagNav) pagNav.classList.add("hidden");
      hideJournalPageLoadingOverlay();
      return;
    }

    statusEl.textContent = "Загрузка…";
    tbody.innerHTML = "";
    if (emptyEl) emptyEl.classList.add("hidden");
    if (tableWrap) tableWrap.classList.remove("hidden");
    if (pagNav) pagNav.classList.add("hidden");

    try {
      const data = await apiGet(buildListUrl());
      const entries = data.entries || [];
      const total = typeof data.total === "number" ? data.total : entries.length;
      const limit = typeof data.limit === "number" ? data.limit : PAGE_SIZE;
      const respOffset = typeof data.offset === "number" ? data.offset : (currentPage - 1) * PAGE_SIZE;
      const totalPages = typeof data.total_pages === "number" ? data.total_pages : Math.ceil(total / limit) || 0;
      let page = typeof data.page === "number" ? data.page : currentPage;

      if (total > 0 && currentPage > totalPages && totalPages > 0) {
        currentPage = totalPages;
        await loadJournal(false);
        return;
      }
      if (total > 0 && entries.length === 0 && respOffset >= total && totalPages > 0) {
        currentPage = totalPages;
        await loadJournal(false);
        return;
      }

      page = currentPage;

      if (!entries.length) {
        if (emptyEl) {
          emptyEl.classList.remove("hidden");
          emptyEl.textContent =
            viewMode === "per_cluster"
              ? "Нет записей в journal/jobs_history.json для этого кластера."
              : viewMode === "provision"
                ? "Нет файлов provision_log.json (или каталогов кластеров)."
                : "Нет файлов helm_addon_log.json — установите аддон со страницы «Аддоны».";
        }
        if (tableWrap) tableWrap.classList.add("hidden");
        statusEl.textContent =
          total === 0
            ? "Записей нет."
            : "Страница пуста (всего: " + total + ").";
        renderPagination(total, limit, respOffset, page, totalPages, 0, goToPage);
        return;
      }

      if (emptyEl) emptyEl.classList.add("hidden");
      if (tableWrap) tableWrap.classList.remove("hidden");

      const fromN = respOffset + 1;
      const toN = respOffset + entries.length;
      statusEl.textContent =
        "Записи " + fromN + "–" + toN + " из " + total + " · страница " + page + " из " + (totalPages || 1) + ".";

      renderTableRows(entries, viewMode);
      renderPagination(total, limit, respOffset, page, totalPages, entries.length, goToPage);
    } catch (err) {
      statusEl.textContent = "Ошибка: " + err.message;
      if (emptyEl) {
        emptyEl.classList.remove("hidden");
        emptyEl.textContent = "Не удалось загрузить данные.";
      }
      if (tableWrap) tableWrap.classList.add("hidden");
      if (pagNav) pagNav.classList.add("hidden");
    } finally {
      hideJournalPageLoadingOverlay();
    }
  }

  async function loadClusterList() {
    if (!clusterSelect) return;
    try {
      const rows = await apiGet("/clusters");
      clusterSelect.innerHTML = "";
      const names = rows
        .map(function (c) { return c.name; })
        .filter(Boolean)
        .sort();
      if (!names.length) {
        const o = document.createElement("option");
        o.value = "";
        o.textContent = "Нет каталогов кластеров";
        clusterSelect.appendChild(o);
        selectedCluster = "";
        return;
      }
      names.forEach(function (name) {
        const o = document.createElement("option");
        o.value = name;
        o.textContent = name;
        clusterSelect.appendChild(o);
      });
      if (!selectedCluster || names.indexOf(selectedCluster) < 0) {
        selectedCluster = names[0];
      }
      clusterSelect.value = selectedCluster;
    } catch (e) {
      clusterSelect.innerHTML = "";
      const o = document.createElement("option");
      o.value = "";
      o.textContent = "Ошибка загрузки списка";
      clusterSelect.appendChild(o);
      selectedCluster = "";
    }
  }

  function bindViewRadios() {
    document.querySelectorAll('input[name="journal_view"]').forEach(function (radio) {
      radio.addEventListener("change", function () {
        if (!radio.checked) return;
        viewMode = radio.value;
        currentPage = 1;
        loadJournal(false);
      });
    });
  }

  if (clusterSelect) {
    clusterSelect.addEventListener("change", function () {
      selectedCluster = String(clusterSelect.value || "").trim();
      currentPage = 1;
      if (viewMode === "per_cluster") loadJournal(false);
    });
  }

  if (refreshBtn) {
    refreshBtn.addEventListener("click", function () { loadJournal(true); });
  }

  bindViewRadios();

  (async function init() {
    await loadClusterList();
    await loadJournal(false);
  })();
})();
