/**
 * Hub «К»: уведомления и план на день. FAB «К», модалка, badge.
 */
(function (global) {
  const MODAL_ID = "zootechNotificationCenterModal";
  const FAB_CLASS = "wesp-zt-notify-fab";
  const POLL_MS = 60000;
  const SCROLL_LOAD_THRESHOLD = 80;

  let fabEl = null;
  let badgeEl = null;
  let modalEl = null;
  let listEl = null;
  let detailEl = null;
  let hubEl = null;
  let planMountEl = null;
  let multiserverMountEl = null;
  let titleEl = null;
  let readAllBtn = null;
  let backBtn = null;
  let normsBtn = null;
  let pollTimer = null;
  let itemsCache = [];
  let currentView = "hub";
  let dailyPlanPanel = null;
  let toolbarEl = null;
  let scrollRootEl = null;
  let categoryFilter = "all";
  let sortMode = "newest";
  let unreadOnly = false;
  let prevDate = null;
  let loadingMore = false;
  let listLoading = false;
  let scrollWired = false;

  const STORAGE_KEY = "wesp-notify-prefs";

  const CATEGORY_LABELS = {
    daily_plan: "План на день",
    recipe: "Рейсы",
    sync: "Синхронизация",
    feed_quality: "Отклонения",
    component: "Компоненты",
    sklad: "Склад",
    equipment: "Оборудование",
    report: "Отчёты",
    general: "Общее",
  };

  const CATEGORY_FILTERS = [
    { id: "all", label: "Все" },
    { id: "daily_plan", label: "План" },
    { id: "recipe", label: "Рейсы" },
    { id: "sync", label: "Синхр." },
    { id: "feed_quality", label: "Отклон." },
    { id: "component", label: "Компон." },
    { id: "sklad", label: "Склад" },
    { id: "equipment", label: "Оборуд." },
  ];

  const KIND_ORDER = { error: 0, warning: 1, info: 2, success: 3 };

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

  function resolveNotificationHref(item) {
    if (!item || !item.linkKind) return null;
    if (item.linkKind === "component") return "/components";
    if (item.linkKind === "sklad") return "/feed_consumption";
    if (item.linkKind === "equipment") return "/feed_dispensers";
    if (!item.linkId) return null;
    const id = encodeURIComponent(item.linkId);
    if (item.linkKind === "recipe") return `/recipes?recipe=${id}`;
    if (item.linkKind === "report_loading") return `/reports?report=${id}`;
    if (item.linkKind === "daily_plan") return `/recipes?dailyPlan=${id}`;
    return null;
  }

  async function markReadQuiet(id) {
    const item = itemsCache.find((x) => x.id === id);
    if (!item || item.read) return;
    try {
      await fetch(`/api/notifications/${encodeURIComponent(id)}/read`, { method: "PATCH" });
      item.read = true;
      await refreshBadge();
    } catch {
      /* ignore */
    }
  }

  async function openNotificationTarget(item) {
    if (isDailyPlanItem(item)) {
      await markReadQuiet(item.id);
      openDailyPlan();
      return;
    }
    const href = resolveNotificationHref(item);
    if (!href) {
      await showDetail(item.id);
      return;
    }
    await markReadQuiet(item.id);
    if (global.bootstrap?.Modal) {
      const inst = global.bootstrap.Modal.getInstance(modalEl);
      inst?.hide();
    }
    global.location.href = href;
  }

  function relativeTime(iso) {
    if (!iso) return "";
    const then = new Date(iso).getTime();
    const diff = Date.now() - then;
    const min = Math.floor(diff / 60000);
    if (min < 1) return "только что";
    if (min < 60) return `${min} мин назад`;
    const hrs = Math.floor(min / 60);
    if (hrs < 24) return `${hrs} ч назад`;
    const days = Math.floor(hrs / 24);
    return `${days} дн назад`;
  }

  function loadPrefs() {
    try {
      const raw = global.sessionStorage?.getItem(STORAGE_KEY);
      if (!raw) return;
      const prefs = JSON.parse(raw);
      if (prefs.categoryFilter) categoryFilter = prefs.categoryFilter;
      if (prefs.sortMode) sortMode = prefs.sortMode;
      if (typeof prefs.unreadOnly === "boolean") unreadOnly = prefs.unreadOnly;
    } catch {
      /* ignore */
    }
  }

  function savePrefs() {
    try {
      global.sessionStorage?.setItem(
        STORAGE_KEY,
        JSON.stringify({ categoryFilter, sortMode, unreadOnly })
      );
    } catch {
      /* ignore */
    }
  }

  function itemTimestamp(item) {
    return item?.createdAt ? new Date(item.createdAt).getTime() : 0;
  }

  function sortItems(items) {
    const list = [...items];
    if (sortMode === "oldest") {
      return list.sort((a, b) => itemTimestamp(a) - itemTimestamp(b));
    }
    if (sortMode === "unread_first") {
      return list.sort((a, b) => {
        const ra = a.read ? 1 : 0;
        const rb = b.read ? 1 : 0;
        if (ra !== rb) return ra - rb;
        return itemTimestamp(b) - itemTimestamp(a);
      });
    }
    if (sortMode === "severity") {
      return list.sort((a, b) => {
        const ka = KIND_ORDER[a.kind] ?? 9;
        const kb = KIND_ORDER[b.kind] ?? 9;
        if (ka !== kb) return ka - kb;
        return itemTimestamp(b) - itemTimestamp(a);
      });
    }
    return list.sort((a, b) => itemTimestamp(b) - itemTimestamp(a));
  }

  function formatDayLabel(isoDate) {
    if (!isoDate) return "Ранее";
    const parts = String(isoDate).slice(0, 10).split("-");
    if (parts.length !== 3) return isoDate;
    const d = new Date(Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
    const now = new Date();
    const startToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    const startThat = new Date(d.getFullYear(), d.getMonth(), d.getDate());
    const diffDays = Math.round((startToday - startThat) / 86400000);
    if (diffDays === 0) return "Сегодня";
    if (diffDays === 1) return "Вчера";
    return d.toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric" });
  }

  function dayKeyFromItem(item) {
    if (!item?.createdAt) return "";
    const d = new Date(item.createdAt);
    const y = d.getFullYear();
    const m = String(d.getMonth() + 1).padStart(2, "0");
    const day = String(d.getDate()).padStart(2, "0");
    return `${y}-${m}-${day}`;
  }

  function inferCategory(item) {
    const raw = item?.category || "general";
    if (raw === "daily_plan") return "daily_plan";
    if (item?.linkKind === "daily_plan" || item?.page === "daily_plan") return "daily_plan";
    const text = `${item?.title || ""} ${item?.detail || ""}`.toLowerCase();
    if (
      text.includes("из плана") ||
      text.includes("в плане") ||
      text.includes("замен") ||
      text.includes("исключ") ||
      text.includes("снова в план") ||
      text.includes("план на день")
    ) {
      return "daily_plan";
    }
    return raw;
  }

  function isDailyPlanItem(item) {
    return inferCategory(item) === "daily_plan";
  }

  function categoryLabel(item) {
    return CATEGORY_LABELS[inferCategory(item)] || CATEGORY_LABELS.general;
  }

  function buildListUrl(date) {
    const params = new URLSearchParams({ sort: sortMode });
    if (date) params.set("date", date);
    if (categoryFilter && categoryFilter !== "all") params.set("category", categoryFilter);
    if (unreadOnly) params.set("unread", "1");
    return `/api/notifications?${params.toString()}`;
  }

  function ensureFab() {
    if (fabEl && fabEl.isConnected) return fabEl;
    fabEl = document.querySelector(`.${FAB_CLASS}`);
    if (!fabEl) {
      fabEl = document.createElement("button");
      fabEl.type = "button";
      fabEl.className = FAB_CLASS;
      fabEl.setAttribute("data-action", "open-notification-center");
      fabEl.setAttribute("aria-label", "Центр «К»");
      fabEl.innerHTML =
        '<span class="wesp-zt-notify-fab__glyph" aria-hidden="true">К</span>' +
        '<span class="wesp-zt-notify-fab__badge" hidden aria-hidden="true"></span>';
      document.body.appendChild(fabEl);
    }
    badgeEl = fabEl.querySelector(".wesp-zt-notify-fab__badge");
    fabEl.addEventListener("click", openCenter);
    return fabEl;
  }

  function categoryFilterButtonsHtml() {
    return CATEGORY_FILTERS.map(
      (f) =>
        `<button type="button" class="zt-tab${f.id === categoryFilter ? " active" : ""}" ` +
        `data-notify-category="${escapeHtml(f.id)}">${escapeHtml(f.label)}</button>`
    ).join("");
  }

  function modalTemplate() {
    return (
      `<div class="modal wesp-shell-modal" id="${MODAL_ID}" tabindex="-1" aria-hidden="true">` +
      '<div class="modal-dialog modal-dialog--shell modal-dialog--shell-wide">' +
      '<div class="modal-content wesp-shell-bsmodal border-0 shadow-none zt-k-hub-modal">' +
      '<div class="zt-k-hub-modal__header">' +
      '<div class="zt-k-hub-modal__header-left">' +
      '<button type="button" class="zt-k-hub-modal__back" data-action="k-hub-back" hidden>&larr; Назад</button>' +
      '<h2 class="wesp-shell-bsmodal-title zt-k-hub-modal__title" data-k-hub-title>Комптон - хаб</h2>' +
      "</div>" +
      '<div class="zt-k-hub-modal__header-right">' +
      '<button type="button" class="btn btn-sm btn-outline-primary zt-k-hub-plan-norms-btn" data-action="k-hub-open-component-norms" hidden title="Изменить СВ% компонентов только в плане на день — рецепты в справочнике не меняются">Коррекция СВ%</button>' +
      '<button type="button" class="zt-notify-center__read-all" data-action="notify-read-all" hidden>Прочитать все</button>' +
      '<button type="button" class="wesp-shell-bsmodal-close" data-bs-dismiss="modal" aria-label="Закрыть">×</button>' +
      "</div></div>" +
      '<div class="zt-k-hub-modal__body">' +
      '<div class="zt-k-hub" data-k-hub-panel></div>' +
      '<div class="zt-notify-center__notify" data-k-notify-panel hidden>' +
      '<div class="zt-notify-toolbar" data-notify-toolbar>' +
      '<div class="zt-notify-toolbar__filters zt-tabs" role="tablist" data-notify-categories>' +
      categoryFilterButtonsHtml() +
      "</div>" +
      '<div class="zt-notify-toolbar__row">' +
      '<label class="zt-notify-toolbar__unread">' +
      '<input type="checkbox" data-notify-unread-only> Только непрочит.' +
      "</label>" +
      '<label class="zt-notify-toolbar__sort">' +
      '<span class="visually-hidden">Сортировка</span>' +
      '<select data-notify-sort aria-label="Сортировка">' +
      '<option value="newest">Сначала новые</option>' +
      '<option value="oldest">Сначала старые</option>' +
      '<option value="unread_first">Непрочит. сверху</option>' +
      '<option value="severity">По важности</option>' +
      "</select></label></div></div>" +
      '<div class="zt-notify-list" data-notify-list></div>' +
      '<div class="zt-notify-load-more" data-notify-load-more hidden></div>' +
      '<div class="zt-notify-detail" data-notify-detail hidden></div>' +
      "</div>" +
      '<div class="zt-k-hub-plan-mount" data-k-plan-panel hidden></div>' +
      '<div class="zt-k-hub-multiserver-mount" data-k-multiserver-panel hidden></div>' +
      "</div></div></div></div>"
    );
  }

  function ensureModal() {
    if (modalEl && modalEl.isConnected) return modalEl;
    modalEl = document.getElementById(MODAL_ID);
    if (!modalEl) {
      document.body.insertAdjacentHTML("beforeend", modalTemplate());
      modalEl = document.getElementById(MODAL_ID);
    }
    hubEl = modalEl.querySelector("[data-k-hub-panel]");
    listEl = modalEl.querySelector("[data-notify-list]");
    detailEl = modalEl.querySelector("[data-notify-detail]");
    toolbarEl = modalEl.querySelector("[data-notify-toolbar]");
    scrollRootEl = modalEl.querySelector(".zt-k-hub-modal__body");
    planMountEl = modalEl.querySelector("[data-k-plan-panel]");
    multiserverMountEl = modalEl.querySelector("[data-k-multiserver-panel]");
    titleEl = modalEl.querySelector("[data-k-hub-title]");
    readAllBtn = modalEl.querySelector("[data-action='notify-read-all']");
    backBtn = modalEl.querySelector("[data-action='k-hub-back']");
    normsBtn = modalEl.querySelector("[data-action='k-hub-open-component-norms']");
    global.WespZootechModals?.wireAllShellModals?.();
    readAllBtn?.addEventListener("click", readAll);
    backBtn?.addEventListener("click", () => showHub());
    normsBtn?.addEventListener("click", () => {
      global.openDailyPlanComponentNormsModal?.();
    });
    wireNotifyToolbar();
    wireScrollLoad();
    hubEl?.querySelectorAll("[data-action]").forEach((btn) => {
      btn.addEventListener("click", () => {
        const action = btn.getAttribute("data-action");
        if (action === "hub-open-notifications") openNotifications();
        if (action === "hub-open-daily-plan") openDailyPlan();
        if (action === "hub-open-multiserver") openMultiserver();
      });
    });
    wireModalLifecycle();
    renderHubCards();
    return modalEl;
  }

  function syncToolbarUi() {
    if (!toolbarEl) return;
    toolbarEl.querySelectorAll("[data-notify-category]").forEach((btn) => {
      const cat = btn.getAttribute("data-notify-category") || "all";
      btn.classList.toggle("active", cat === categoryFilter);
    });
    const sortSelect = toolbarEl.querySelector("[data-notify-sort]");
    if (sortSelect) sortSelect.value = sortMode;
    const unreadCb = toolbarEl.querySelector("[data-notify-unread-only]");
    if (unreadCb) unreadCb.checked = unreadOnly;
  }

  function wireNotifyToolbar() {
    if (!toolbarEl || toolbarEl.dataset.wired === "1") return;
    toolbarEl.dataset.wired = "1";
    loadPrefs();
    syncToolbarUi();
    toolbarEl.querySelectorAll("[data-notify-category]").forEach((btn) => {
      btn.addEventListener("click", () => {
        categoryFilter = btn.getAttribute("data-notify-category") || "all";
        savePrefs();
        syncToolbarUi();
        reloadNotifications();
      });
    });
    toolbarEl.querySelector("[data-notify-sort]")?.addEventListener("change", (ev) => {
      sortMode = ev.target.value || "newest";
      savePrefs();
      reloadNotifications();
    });
    toolbarEl.querySelector("[data-notify-unread-only]")?.addEventListener("change", (ev) => {
      unreadOnly = Boolean(ev.target.checked);
      savePrefs();
      reloadNotifications();
    });
  }

  function wireScrollLoad() {
    if (scrollWired || !scrollRootEl) return;
    scrollWired = true;
    scrollRootEl.addEventListener("scroll", () => {
      if (currentView !== "notifications" || !prevDate || loadingMore || listLoading) return;
      const nearBottom =
        scrollRootEl.scrollTop + scrollRootEl.clientHeight >=
        scrollRootEl.scrollHeight - SCROLL_LOAD_THRESHOLD;
      if (nearBottom) loadMoreNotifications();
    });
  }

  function itemPreview(item) {
    const detail = String(item?.detail || "").trim();
    if (detail) {
      const cut = detail.replace(/\.\s+\d{1,2}\s+\S+\s+\d{4}.*$/u, "").trim();
      const text = cut || detail;
      return text.length > 140 ? `${text.slice(0, 137)}…` : text;
    }
    return item?.title || "";
  }

  function renderNotifyItem(item) {
    const unread = !item.read ? " zt-notify-item--unread" : "";
    const cat = categoryLabel(item);
    const preview = itemPreview(item);
    const showPreview = preview && preview !== item.title;
    return (
      `<button type="button" class="zt-notify-item${unread}" data-notify-id="${escapeHtml(item.id)}">` +
      `<span class="zt-notify-item__dot zt-notify-item__dot--${escapeHtml(item.kind || "info")}"></span>` +
      `<span class="zt-notify-item__main">` +
      `<span class="zt-notify-item__title">${escapeHtml(item.title)}</span>` +
      (showPreview
        ? `<span class="zt-notify-item__preview">${escapeHtml(preview)}</span>`
        : "") +
      `<span class="zt-notify-item__meta">` +
      `<span class="zt-notify-item__category">${escapeHtml(cat)}</span>` +
      `<span class="zt-notify-item__time">${escapeHtml(relativeTime(item.createdAt))}</span>` +
      "</span></span></button>"
    );
  }

  function renderLoadMoreState() {
    const el = modalEl?.querySelector("[data-notify-load-more]");
    if (!el) return;
    if (loadingMore) {
      el.hidden = false;
      el.innerHTML =
        '<div class="zt-notify-load-more__spinner"><div class="zt-spinner" role="status"></div>' +
        '<span>Загрузка…</span></div>';
      return;
    }
    if (prevDate) {
      el.hidden = false;
      el.innerHTML =
        `<p class="zt-notify-load-more__hint">Прокрутите вниз — загрузятся уведомления за ${escapeHtml(formatDayLabel(prevDate))}</p>`;
      return;
    }
    el.hidden = true;
    el.innerHTML = "";
  }

  function renderHubCards() {
    if (!hubEl) return;
    hubEl.innerHTML =
      '<div class="zt-k-hub__grid">' +
      '<button type="button" class="zt-k-hub-card zt-card zt-card--interactive" data-action="hub-open-notifications">' +
      '<span class="zt-k-hub-card__icon" aria-hidden="true"><i class="fas fa-bell"></i></span>' +
      '<span class="zt-k-hub-card__text">' +
      '<span class="zt-k-hub-card__title">Уведомления</span>' +
      '<span class="zt-k-hub-card__desc">План, рейсы, синхронизация и отклонения</span>' +
      "</span></button>" +
      '<button type="button" class="zt-k-hub-card zt-card zt-card--interactive" data-action="hub-open-daily-plan">' +
      '<span class="zt-k-hub-card__icon" aria-hidden="true"><i class="fas fa-calendar-day"></i></span>' +
      '<span class="zt-k-hub-card__text">' +
      '<span class="zt-k-hub-card__title">План на день</span>' +
      '<span class="zt-k-hub-card__desc">Рейсы, ингредиенты и выгрузка по периодам</span>' +
      "</span></button>" +
      '<button type="button" class="zt-k-hub-card zt-card zt-card--interactive" data-action="hub-open-multiserver">' +
      '<span class="zt-k-hub-card__icon" aria-hidden="true"><i class="fas fa-server"></i></span>' +
      '<span class="zt-k-hub-card__text">' +
      '<span class="zt-k-hub-card__title">Мультисервер</span>' +
      '<span class="zt-k-hub-card__desc">Конфликты синхронизации оркестр ↔ фермы</span>' +
      "</span></button></div>";
    hubEl.querySelectorAll("[data-action]").forEach((btn) => {
      btn.addEventListener("click", () => {
        const action = btn.getAttribute("data-action");
        if (action === "hub-open-notifications") openNotifications();
        if (action === "hub-open-daily-plan") openDailyPlan();
        if (action === "hub-open-multiserver") openMultiserver();
      });
    });
  }

  function applyModalLayout(view) {
    if (!modalEl) return;
    const dialog = modalEl.querySelector(".modal-dialog");
    const body = modalEl.querySelector(".zt-k-hub-modal__body");
    modalEl.classList.remove("zt-k-hub--plan", "zt-k-hub--notify");
    body?.classList.remove("zt-k-hub-modal__body--plan");
    dialog?.classList.remove("modal-dialog--shell-plan", "modal-dialog--shell-notify");
    if (view === "daily-plan") {
      modalEl.classList.add("zt-k-hub--plan");
      body?.classList.add("zt-k-hub-modal__body--plan");
      dialog?.classList.add("modal-dialog--shell-plan");
    } else if (view === "notifications") {
      modalEl.classList.add("zt-k-hub--notify");
      dialog?.classList.add("modal-dialog--shell-notify");
    }
  }

  function updateHeader(view) {
    currentView = view;
    applyModalLayout(view);
    if (!titleEl || !backBtn || !readAllBtn) return;
    if (normsBtn) normsBtn.hidden = view !== "daily-plan";
    if (view === "hub") {
      titleEl.textContent = "Комптон - хаб";
      backBtn.hidden = true;
      readAllBtn.hidden = true;
    } else if (view === "notifications") {
      titleEl.textContent = "Уведомления";
      backBtn.hidden = false;
      readAllBtn.hidden = false;
    } else if (view === "daily-plan") {
      titleEl.textContent = "План на день";
      backBtn.hidden = false;
      readAllBtn.hidden = true;
    } else if (view === "multiserver") {
      titleEl.textContent = "Мультисервер";
      backBtn.hidden = false;
      readAllBtn.hidden = true;
    }
  }

  function dismissPlanOverlays() {
    modalEl?.querySelectorAll(".zt-k-hub-plan-overlay").forEach((el) => el.remove());
  }

  function destroyDailyPlanPanel() {
    dismissPlanOverlays();
    global.closeDailyPlanComponentNormsModal?.();
    dailyPlanPanel?.destroy?.();
    dailyPlanPanel = null;
    global.WespDailyPlanPanel?._setActivePanel?.(null);
  }

  function wireModalLifecycle() {
    ensureModal();
    if (modalEl.dataset.kHubLifecycleWired === "1") return;
    modalEl.dataset.kHubLifecycleWired = "1";
    modalEl.addEventListener("hide.bs.modal", dismissPlanOverlays);
    modalEl.addEventListener("hidden.bs.modal", destroyDailyPlanPanel);
  }

  function showHub() {
    ensureModal();
    updateHeader("hub");
    hubEl.hidden = false;
    modalEl.querySelector("[data-k-notify-panel]").hidden = true;
    planMountEl.hidden = true;
    if (multiserverMountEl) multiserverMountEl.hidden = true;
    destroyDailyPlanPanel();
    listEl.hidden = false;
    detailEl.hidden = true;
  }

  function setBadge(unreadCount) {
    ensureFab();
    if (!badgeEl) return;
    const count = Number(unreadCount) || 0;
    if (count <= 0) {
      badgeEl.hidden = true;
      badgeEl.textContent = "";
      fabEl.classList.remove("has-unread");
      return;
    }
    badgeEl.hidden = false;
    badgeEl.textContent = count > 9 ? "9+" : String(count);
    fabEl.classList.add("has-unread");
  }

  async function refreshBadge() {
    try {
      const resp = await fetch("/api/notifications?summary=1");
      if (!resp.ok) return;
      const data = await resp.json();
      setBadge(data.unreadCount);
    } catch {
      /* ignore */
    }
  }

  function mergeItems(existing, incoming) {
    const map = new Map(existing.map((x) => [x.id, x]));
    incoming.forEach((item) => map.set(item.id, item));
    return Array.from(map.values());
  }

  function matchesCategoryFilter(item) {
    if (!categoryFilter || categoryFilter === "all") return true;
    return inferCategory(item) === categoryFilter;
  }

  function filterVisibleItems(items) {
    let list = items.filter(matchesCategoryFilter);
    if (unreadOnly) list = list.filter((x) => !x.read);
    return list;
  }

  async function fetchDay(date) {
    const resp = await fetch(buildListUrl(date));
    if (!resp.ok) throw new Error("Не удалось загрузить уведомления");
    return resp.json();
  }

  async function loadInitialNotifications() {
    let date = null;
    let found = [];
    for (let step = 0; step < 31; step += 1) {
      const data = await fetchDay(date);
      setBadge(data.unreadCount);
      if (data.items?.length) {
        found = mergeItems(found, data.items);
        prevDate = data.hasMore ? data.prevDate : null;
        return found;
      }
      if (!data.hasMore || !data.prevDate) {
        prevDate = null;
        return found;
      }
      date = data.prevDate;
    }
    prevDate = null;
    return found;
  }

  async function reloadNotifications() {
    listLoading = true;
    itemsCache = [];
    prevDate = null;
    detailEl.hidden = true;
    listEl.hidden = false;
    listEl.innerHTML =
      '<div class="zt-notify-empty zt-notify-empty--loading">' +
      '<div class="zt-spinner" role="status"></div><p>Загрузка…</p></div>';
    renderLoadMoreState();
    try {
      itemsCache = await loadInitialNotifications();
      renderList();
    } catch {
      itemsCache = [];
      prevDate = null;
      renderList();
    } finally {
      listLoading = false;
      renderLoadMoreState();
    }
  }

  async function loadMoreNotifications() {
    if (!prevDate || loadingMore || listLoading) return;
    loadingMore = true;
    renderLoadMoreState();
    const loadDate = prevDate;
    try {
      const data = await fetchDay(loadDate);
      itemsCache = mergeItems(itemsCache, data.items || []);
      prevDate = data.hasMore ? data.prevDate : null;
      setBadge(data.unreadCount);
      renderList();
    } catch {
      /* keep current list */
    } finally {
      loadingMore = false;
      renderLoadMoreState();
    }
  }

  function wireListClicks() {
    listEl.querySelectorAll("[data-notify-id]").forEach((btn) => {
      btn.addEventListener("click", () => {
        const id = btn.getAttribute("data-notify-id");
        if (id) showDetail(id);
      });
    });
  }

  function renderList() {
    ensureModal();
    wireNotifyToolbar();
    syncToolbarUi();
    detailEl.hidden = true;
    listEl.hidden = false;
    const sorted = sortItems(filterVisibleItems(itemsCache));
    if (!sorted.length) {
      const hint =
        unreadOnly || categoryFilter !== "all"
          ? prevDate
            ? "За сегодня ничего нет — прокрутите вниз для более ранних"
            : "Нет уведомлений по выбранному фильтру"
          : "Нет уведомлений";
      listEl.innerHTML =
        `<div class="zt-notify-empty"><i class="fas fa-bell-slash"></i><p>${escapeHtml(hint)}</p></div>`;
      renderLoadMoreState();
      return;
    }

    let html = "";
    let lastGroup = null;
    sorted.forEach((item) => {
      const group = dayKeyFromItem(item);
      if (group !== lastGroup) {
        html += `<div class="zt-notify-group">${escapeHtml(formatDayLabel(group))}</div>`;
        lastGroup = group;
      }
      html += renderNotifyItem(item);
    });
    listEl.innerHTML = html;
    wireListClicks();
    renderLoadMoreState();
  }

  async function showDetail(id) {
    const item = itemsCache.find((x) => x.id === id);
    if (!item) return;
    ensureModal();
    listEl.hidden = true;
    modalEl.querySelector("[data-notify-load-more]")?.setAttribute("hidden", "");
    detailEl.hidden = false;
    detailEl.innerHTML =
      `<button type="button" class="zt-notify-detail__back" data-action="notify-back">&larr; Назад</button>` +
      `<p class="zt-notify-detail__kind zt-notify-detail__kind--${escapeHtml(item.kind)}">${escapeHtml(item.title)}</p>` +
      `<p class="zt-notify-detail__meta">${escapeHtml(categoryLabel(item))}</p>` +
      `<p class="zt-notify-detail__text">${escapeHtml(item.detail)}</p>` +
      `<p class="zt-notify-detail__when">${escapeHtml(relativeTime(item.createdAt))}</p>` +
      (resolveNotificationHref(item) || isDailyPlanItem(item)
        ? `<button type="button" class="btn btn-primary btn-sm mt-3" data-action="notify-open-target">Открыть план</button>`
        : "");
    detailEl.querySelector("[data-action='notify-open-target']")?.addEventListener("click", () => {
      openNotificationTarget(item);
    });
    detailEl.querySelector("[data-action='notify-back']")?.addEventListener("click", () => {
      renderList();
    });
    if (!item.read) {
      try {
        await fetch(`/api/notifications/${encodeURIComponent(id)}/read`, { method: "PATCH" });
        item.read = true;
        await refreshBadge();
      } catch {
        /* ignore */
      }
    }
  }

  async function readAll() {
    try {
      await fetch("/api/notifications/read-all", { method: "PATCH" });
      await reloadNotifications();
    } catch {
      /* ignore */
    }
  }

  function showModal() {
    if (global.WespZootechModals?.show) {
      global.WespZootechModals.show(MODAL_ID);
    } else if (global.bootstrap?.Modal) {
      global.bootstrap.Modal.getOrCreateInstance(modalEl).show();
    }
  }

  async function openCenter() {
    ensureModal();
    showHub();
    showModal();
  }

  async function openNotifications() {
    ensureModal();
    updateHeader("notifications");
    hubEl.hidden = true;
    modalEl.querySelector("[data-k-notify-panel]").hidden = false;
    planMountEl.hidden = true;
    destroyDailyPlanPanel();
    await reloadNotifications();
    showModal();
  }

  function openDailyPlan() {
    ensureModal();
    updateHeader("daily-plan");
    hubEl.hidden = true;
    modalEl.querySelector("[data-k-notify-panel]").hidden = true;
    planMountEl.hidden = false;
    destroyDailyPlanPanel();
    dailyPlanPanel = global.WespDailyPlanPanel?.createDailyPlanPanel?.();
    if (dailyPlanPanel && planMountEl) {
      dailyPlanPanel.mount(planMountEl);
      global.WespDailyPlanPanel?._setActivePanel?.(dailyPlanPanel);
    } else if (planMountEl) {
      planMountEl.innerHTML =
        '<div class="zt-k-hub-plan__empty"><p>Модуль плана на день не загружен.</p></div>';
    }
    showModal();
  }

  function openMultiserver() {
    ensureModal();
    updateHeader("multiserver");
    hubEl.hidden = true;
    modalEl.querySelector("[data-k-notify-panel]").hidden = true;
    planMountEl.hidden = true;
    multiserverMountEl.hidden = false;
    destroyDailyPlanPanel();
    const enterpriseId = global.__WESP_ORCH__?.enterpriseId || "";
    if (global.WespMultiserverPanel?.mount) {
      global.WespMultiserverPanel.mount(multiserverMountEl, enterpriseId);
    } else if (multiserverMountEl) {
      multiserverMountEl.innerHTML =
        '<div class="zt-k-hub-plan__empty"><p>Модуль мультисервера не загружен.</p></div>';
    }
    showModal();
  }

  function init() {
    if (!document.body.classList.contains("zootech-app-body")) return;
    loadPrefs();
    ensureFab();
    ensureModal();
    refreshBadge();
    if (pollTimer) clearInterval(pollTimer);
    pollTimer = setInterval(refreshBadge, POLL_MS);
  }

  global.WespZootechNotificationCenter = {
    init,
    openCenter,
    openNotifications,
    openDailyPlan,
    openMultiserver,
    refreshBadge,
    setBadge,
  };

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init);
  } else {
    init();
  }
})(typeof window !== "undefined" ? window : globalThis);
