import { ApiClient, IocStore, supportedTypes, verdictMeta, verdictPriority } from "./data.js";
import { manualApiBaseUrl } from "./config.js";
import { WidgetManager } from "./widgets.js";

const SCAN_POLL_DELAY = 2000;
const HEALTH_POLL_DELAY = 5000;
const THEME_STORAGE_KEY = "ioc-theme";
const HISTORY_STORAGE_KEY = "ioc-history";
const HISTORY_LIMIT = 30;

const vendorPathAliases = new Map([
  ["vendor/bootstrap/css/", "bootstrap@5.3.3/dist/css/"],
  ["vendor/bootstrap/js/", "bootstrap@5.3.3/dist/js/"],
  ["vendor/bootstrap-icons/", "bootstrap-icons@1.11.3/"],
  ["vendor/gridstack/", "gridstack@10.1.2/dist/"],
  ["vendor/bootswatch/", "bootswatch@5.3.3/dist/"],
]);

function withTrailingSlash(path) {
  return path.endsWith("/") ? path : `${path}/`;
}

const STATIC_BASE = withTrailingSlash(window.STATIC_BASE || "/static/verificator/");
const VENDOR_BASE = withTrailingSlash(window.VENDOR_BASE || "https://cdn.jsdelivr.net/npm/");

function resolveAsset(path) {
  if (path.startsWith("http")) {
    return path;
  }

  for (const [prefix, replacement] of vendorPathAliases.entries()) {
    if (path.startsWith(prefix)) {
      return `${VENDOR_BASE}${replacement}${path.slice(prefix.length)}`;
    }
  }

  return `${STATIC_BASE}${path}`;
}

const themes = [
  {
    id: "default",
    title: "По умолчанию",
    file: "vendor/bootstrap/css/bootstrap.min.css",
    mode: "light",
    hint: "Стандартный Bootstrap",
    icon: "bi-bootstrap",
  },
  {
    id: "brite",
    title: "Яркий",
    file: "vendor/bootswatch/brite/bootstrap.min.css",
    mode: "light",
    hint: "Контрастные акценты",
    icon: "bi-stars",
  },
  {
    id: "cerulean",
    title: "Лазурь",
    file: "vendor/bootswatch/cerulean/bootstrap.min.css",
    mode: "light",
    hint: "Небесные оттенки",
    icon: "bi-cloudy",
  },
  {
    id: "cosmo",
    title: "Космо",
    file: "vendor/bootswatch/cosmo/bootstrap.min.css",
    mode: "light",
    hint: "Плотные цвета",
    icon: "bi-diamond",
  },
  {
    id: "cyborg",
    title: "Киборг",
    file: "vendor/bootswatch/cyborg/bootstrap.min.css",
    mode: "dark",
    hint: "Глубокий тёмный",
    icon: "bi-cpu",
  },
  {
    id: "darkly",
    title: "Ночь",
    file: "vendor/bootswatch/darkly/bootstrap.min.css",
    mode: "dark",
    hint: "Контрастная тёмная",
    icon: "bi-moon-stars",
  },
  {
    id: "flatly",
    title: "Плоский",
    file: "vendor/bootswatch/flatly/bootstrap.min.css",
    mode: "light",
    hint: "Плоские акценты",
    icon: "bi-layers",
  },
  {
    id: "journal",
    title: "Журнал",
    file: "vendor/bootswatch/journal/bootstrap.min.css",
    mode: "light",
    hint: "Газетная подача",
    icon: "bi-journal-text",
  },
  {
    id: "litera",
    title: "Литера",
    file: "vendor/bootswatch/litera/bootstrap.min.css",
    mode: "light",
    hint: "Читаемый шрифт",
    icon: "bi-type",
  },
  {
    id: "lumen",
    title: "Люмен",
    file: "vendor/bootswatch/lumen/bootstrap.min.css",
    mode: "light",
    hint: "Светлая и воздушная",
    icon: "bi-sun",
  },
  {
    id: "lux",
    title: "Люкс",
    file: "vendor/bootswatch/lux/bootstrap.min.css",
    mode: "light",
    hint: "Минималистичная",
    icon: "bi-gem",
  },
  {
    id: "materia",
    title: "Материя",
    file: "vendor/bootswatch/materia/bootstrap.min.css",
    mode: "light",
    hint: "Материальные слои",
    icon: "bi-layers-half",
  },
  {
    id: "minty",
    title: "Мята",
    file: "vendor/bootswatch/minty/bootstrap.min.css",
    mode: "light",
    hint: "Мятные акценты",
    icon: "bi-droplet",
  },
  {
    id: "morph",
    title: "Морф",
    file: "vendor/bootswatch/morph/bootstrap.min.css",
    mode: "light",
    hint: "Плавные формы",
    icon: "bi-magic",
  },
  {
    id: "pulse",
    title: "Пульс",
    file: "vendor/bootswatch/pulse/bootstrap.min.css",
    mode: "light",
    hint: "Яркие кнопки",
    icon: "bi-heart-pulse",
  },
  {
    id: "quartz",
    title: "Кварц",
    file: "vendor/bootswatch/quartz/bootstrap.min.css",
    mode: "dark",
    hint: "Фиолетовые акценты",
    icon: "bi-gem",
  },
  {
    id: "sandstone",
    title: "Песок",
    file: "vendor/bootswatch/sandstone/bootstrap.min.css",
    mode: "light",
    hint: "Тёплые оттенки",
    icon: "bi-bezier",
  },
  {
    id: "simplex",
    title: "Простой",
    file: "vendor/bootswatch/simplex/bootstrap.min.css",
    mode: "light",
    hint: "Простая графика",
    icon: "bi-border",
  },
  {
    id: "sketchy",
    title: "Эскиз",
    file: "vendor/bootswatch/sketchy/bootstrap.min.css",
    mode: "light",
    hint: "Ручной набросок",
    icon: "bi-pencil",
  },
  {
    id: "slate",
    title: "Сланец",
    file: "vendor/bootswatch/slate/bootstrap.min.css",
    mode: "dark",
    hint: "Графитовый стиль",
    icon: "bi-hexagon",
  },
  {
    id: "solar",
    title: "Соляр",
    file: "vendor/bootswatch/solar/bootstrap.min.css",
    mode: "dark",
    hint: "Солнечный тёмный",
    icon: "bi-brightness-alt-high",
  },
  {
    id: "spacelab",
    title: "Космос",
    file: "vendor/bootswatch/spacelab/bootstrap.min.css",
    mode: "light",
    hint: "Строгий космос",
    icon: "bi-kanban",
  },
  {
    id: "superhero",
    title: "Герой",
    file: "vendor/bootswatch/superhero/bootstrap.min.css",
    mode: "dark",
    hint: "Геройские контрасты",
    icon: "bi-shield-shaded",
  },
  {
    id: "united",
    title: "Единый",
    file: "vendor/bootswatch/united/bootstrap.min.css",
    mode: "light",
    hint: "Акцент на кнопках",
    icon: "bi-toggle2-on",
  },
  {
    id: "vapor",
    title: "Пар",
    file: "vendor/bootswatch/vapor/bootstrap.min.css",
    mode: "dark",
    hint: "Неоновые огни",
    icon: "bi-lightning-charge",
  },
  {
    id: "yeti",
    title: "Йети",
    file: "vendor/bootswatch/yeti/bootstrap.min.css",
    mode: "light",
    hint: "Лёгкий фирменный",
    icon: "bi-snow",
  },
  {
    id: "zephyr",
    title: "Зефир",
    file: "vendor/bootswatch/zephyr/bootstrap.min.css",
    mode: "light",
    hint: "Мягкий штрих",
    icon: "bi-wind",
  },
];

const iocStore = new IocStore();
const apiClient = new ApiClient(manualApiBaseUrl);
const parsedViews = new Set();
const typeTitle = new Map(supportedTypes.map((type) => [type.id, type.title]));
const typeTone = new Map(supportedTypes.map((type) => [type.id, type.tone]));
const tagDecorations = {
  malwarebazaar: {
    icon: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0iI2ZmN2IwMCI+PHBhdGggZD0iTTggMGw2IDJ2NWMwIDMuNS0yLjYgNi41LTYgOC0zLjQtMS41LTYtNC41LTYtOFYybDYtMnoiLz48L3N2Zz4=",
    label: "MalwareBazaar",
  },
};
const filterState = { hiddenVerdicts: new Set(), hiddenTypes: new Set() };
const scanState = { status: "idle", error: null, taskId: null, updatedAt: null };
const healthState = {
  status: "unknown",
  queuedTasks: 0,
  queuedIocs: 0,
  error: null,
  checkedAt: null,
};
const healthTargets = new Map();
let healthPollTimer = null;
let lastStatusToastKey = null;
const inputBindings = new Set();
const historyEntries = [];
const historyViews = new Map();

function setInputText(text, { silent = false } = {}) {
  const value = text || "";
  if (!inputBindings.size) {
    console.warn("[UI] Некому передать текст из истории: нет полей ввода");
    return;
  }

  inputBindings.forEach((field) => {
    field.value = value;
    if (!silent) {
      field.dispatchEvent(new Event("input", { bubbles: true }));
    }
  });
}

function buildHistoryPreview(text) {
  const normalized = (text || "").replace(/\s+/g, " ").trim();
  if (normalized.length <= 180) {
    return normalized;
  }

  return `${normalized.slice(0, 180).trimEnd()}…`;
}

function renderHistoryContent(target) {
  target.innerHTML = "";

  if (!historyEntries.length) {
    const empty = document.createElement("div");
    empty.className = "text-muted small";
    empty.textContent = "История сканирований появится после первых задач";
    target.appendChild(empty);
    return;
  }

  const list = document.createElement("div");
  list.className = "list-group history-list";

  historyEntries.forEach((entry) => {
    const item = document.createElement("button");
    item.type = "button";
    item.className = "list-group-item list-group-item-action d-flex align-items-start justify-content-between gap-3 history-entry";
    item.addEventListener("click", () => loadTaskFromHistory(entry));

    const intro = document.createElement("div");
    intro.className = "d-flex flex-column gap-1 flex-grow-1 text-start";

    const header = document.createElement("div");
    header.className = "d-flex align-items-center gap-2";

    const taskLabel = document.createElement("span");
    taskLabel.className = "fw-semibold";
    taskLabel.textContent = `#${entry.taskId}`;

    const preview = document.createElement("span");
    preview.className = "small text-muted history-preview";
    preview.textContent = entry.preview;

    header.appendChild(taskLabel);
    intro.appendChild(header);
    intro.appendChild(preview);

    item.appendChild(intro);
    list.appendChild(item);
  });

  target.appendChild(list);
}

function renderHistoryViews() {
  historyViews.forEach((element) => renderHistoryContent(element));
}

function persistHistory() {
  try {
    const compact = historyEntries.map((entry) => ({ taskId: entry.taskId, preview: entry.preview }));
    localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(compact));
  } catch (error) {
    console.warn("[UI] Не удалось сохранить историю задач", error);
  }
}

function restoreHistoryFromStorage() {
  const raw = localStorage.getItem(HISTORY_STORAGE_KEY);
  if (!raw) {
    return;
  }

  try {
    const saved = JSON.parse(raw);
    if (!Array.isArray(saved)) {
      console.warn("[UI] История задач в localStorage повреждена: не массив");
      return;
    }

    historyEntries.splice(0, historyEntries.length);
    saved.slice(0, HISTORY_LIMIT).forEach((entry) => {
      if (!entry?.taskId) {
        return;
      }

      historyEntries.push({
        taskId: entry.taskId,
        preview: entry.preview || "",
      });
    });
  } catch (error) {
    console.warn("[UI] Не удалось восстановить историю задач", error);
  }
}

function unregisterHistoryView(instanceId) {
  historyViews.delete(instanceId);
}

function addHistoryEntry(taskId, text, { keepOrder = false } = {}) {
  if (!taskId) {
    return;
  }

  const normalizedText = (text || "").trim();
  const entry = {
    taskId,
    preview: buildHistoryPreview(normalizedText),
  };

  const existingIndex = historyEntries.findIndex((item) => item.taskId === taskId);
  const entryExists = existingIndex !== -1;

  if (keepOrder && entryExists) {
    historyEntries.splice(existingIndex, 1, entry);
  } else {
    if (entryExists) {
      historyEntries.splice(existingIndex, 1);
    }

    historyEntries.unshift(entry);
    if (historyEntries.length > HISTORY_LIMIT) {
      historyEntries.pop();
    }
  }

  persistHistory();
  renderHistoryViews();
}

function getToastContainer() {
  const container = document.getElementById("toast-container");
  if (!container) {
    console.error("[UI] Контейнер тостов не найден");
  }
  return container;
}

function showToast({ title, message, tone = "primary", icon = "bi-info-circle", delay = 5000 }) {
  const container = getToastContainer();
  if (!container) {
    return;
  }

  const toast = document.createElement("div");
  toast.className = `toast align-items-center text-bg-${tone} border-0 shadow`;
  toast.setAttribute("role", "alert");
  toast.setAttribute("aria-live", "assertive");
  toast.setAttribute("aria-atomic", "true");
  toast.innerHTML = `
    <div class="d-flex">
      <div class="toast-body d-flex align-items-start gap-2">
        <i class="bi ${icon}"></i>
        <div>
          <div class="fw-semibold">${title}</div>
          <div class="small">${message}</div>
        </div>
      </div>
      <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Закрыть"></button>
    </div>
  `;

  container.appendChild(toast);

  const toastInstance = new bootstrap.Toast(toast, { autohide: true, delay });
  toast.addEventListener("hidden.bs.toast", () => toast.remove());
  toastInstance.show();
}

function renderApiStatus(container) {
  if (!container) {
    return;
  }

  const dot = container.querySelector(".api-status-dot");
  const title = container.querySelector(".api-status-title");
  const note = container.querySelector(".api-status-note");
  const time = container.querySelector(".api-status-time");
  const isOnline = healthState.status === "online";
  const isOffline = healthState.status === "offline";

  if (dot) {
    const dotTone = isOnline ? "bg-success" : isOffline ? "bg-danger" : "bg-secondary";
    dot.className = `api-status-dot rounded-circle ${dotTone}`;
  }

  if (title) {
    title.textContent = "API";
    title.classList.toggle("text-success", isOnline);
    title.classList.toggle("text-danger", isOffline);
    title.classList.toggle("text-secondary", !isOnline && !isOffline);
  }

  if (note) {
    if (isOnline) {
      note.textContent = `Очередь: ${healthState.queuedTasks} • IOC: ${healthState.queuedIocs}`;
      note.classList.remove("text-danger");
      note.classList.add("text-success");
    } else if (isOffline) {
      note.textContent = healthState.error || "Нет соединения";
      note.classList.remove("text-success");
      note.classList.add("text-danger");
    } else {
      note.textContent = "Синхронизируем статус...";
      note.classList.remove("text-success", "text-danger");
    }
  }

  if (time) {
    const lastChecked = healthState.checkedAt
      ? new Date(healthState.checkedAt).toLocaleTimeString("ru-RU", { hour12: false })
      : "—";
    time.textContent = `Обновлено: ${lastChecked}`;
    time.classList.toggle("text-success", isOnline);
    time.classList.toggle("text-danger", isOffline);
    time.classList.toggle("text-muted", !isOnline && !isOffline);
  }
}

function renderHealthIndicators() {
  healthTargets.forEach((element) => renderApiStatus(element));
}

function setHealthState(nextState) {
  Object.assign(healthState, nextState);
  renderHealthIndicators();
}

async function refreshHealthStatus() {
  if (!healthTargets.size) {
    return;
  }

  try {
    const payload = await apiClient.getHealth();
    setHealthState({
      status: "online",
      queuedTasks: payload?.queued_tasks ?? 0,
      queuedIocs: payload?.queued_iocs ?? 0,
      error: null,
      checkedAt: Date.now(),
    });
  } catch (error) {
    console.error("[UI] Не удалось получить состояние API", error);
    setHealthState({
      status: "offline",
      error: error.message,
      queuedTasks: 0,
      queuedIocs: 0,
      checkedAt: Date.now(),
    });
  }
}

function startHealthMonitor() {
  if (healthPollTimer || !healthTargets.size) {
    return;
  }

  renderHealthIndicators();
  refreshHealthStatus();
  healthPollTimer = setInterval(() => refreshHealthStatus(), HEALTH_POLL_DELAY);
}

function stopHealthMonitor() {
  if (!healthPollTimer) {
    return;
  }

  clearInterval(healthPollTimer);
  healthPollTimer = null;
}

function registerHealthTarget(id, element) {
  if (!element) {
    return;
  }

  healthTargets.set(id, element);
  renderApiStatus(element);
  startHealthMonitor();
}

function unregisterHealthTarget(id) {
  healthTargets.delete(id);
  if (!healthTargets.size) {
    stopHealthMonitor();
  }
}

function notifyScanStatus() {
  const { status, error, taskId } = scanState;
  let payload = null;

  if (!status || status === "idle" || status === "done") {
    lastStatusToastKey = null;
    return;
  }

  if (status === "preparing") {
    if (lastStatusToastKey !== "scan-started") {
      payload = {
        title: "Начинаем сканирование",
        message: taskId
          ? `Готовим индикаторы, заявка #${taskId} запущена.`
          : "Готовим индикаторы к сканированию.",
        tone: "primary",
        icon: "bi-hourglass-split",
      };
      lastStatusToastKey = "scan-started";
    }
  }

  if (status === "error" || (!status || status === "idle")) {
    const errorText = error || (status === "error" ? "Сканирование завершилось ошибкой." : null);
    if (errorText) {
      payload = {
        title: "Ошибка сканирования",
        message: errorText,
        tone: "danger",
        icon: "bi-exclamation-octagon",
      };
    }
  }

  if (!payload) {
    return;
  }

  showToast(payload);
  if (status === "error") {
    lastStatusToastKey = `${status}|${payload.message}`;
  }
}

function applyTheme(themeId) {
  const theme = themes.find((item) => item.id === themeId) || themes[0];
  const link = document.getElementById("theme-stylesheet");
  const label = document.getElementById("active-theme-name");

  if (!link) {
    console.error("[UI] Не найден тег стилей для темы");
    return;
  }

  link.setAttribute("href", resolveAsset(theme.file));
  document.documentElement.setAttribute("data-bs-theme", theme.mode);
  if (label) {
    label.textContent = theme.title;
  }
  localStorage.setItem(THEME_STORAGE_KEY, theme.id);

  document.querySelectorAll("#theme-menu button").forEach((button) => {
    const isActive = button.dataset.themeId === theme.id;
    button.classList.toggle("active", isActive);
    button.setAttribute("aria-current", isActive ? "true" : "false");
  });

  console.info(`[UI] Применена тема ${theme.title} (${theme.mode})`);
}

function buildThemeMenu() {
  const menu = document.getElementById("theme-menu");
  if (!menu) {
    console.error("[UI] Не найдена панель выбора темы");
    return;
  }
  const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
  const fallbackTheme = themes.find((theme) => theme.id === savedTheme) ? savedTheme : themes[0].id;

  menu.innerHTML = "";

  themes.forEach((theme) => {
    const item = document.createElement("li");
    const button = document.createElement("button");
    button.type = "button";
    button.className = "dropdown-item d-flex align-items-center justify-content-between gap-3 theme-option";
    button.dataset.themeId = theme.id;
    button.innerHTML = `
      <span class="d-flex align-items-center gap-2 theme-title">
        <span class="theme-swatch border theme-${theme.mode}"></span>
        <span class="fw-semibold">${theme.title}</span>
      </span>
      <span class="d-flex align-items-center gap-2 theme-hint small">
        <i class="bi ${theme.icon}"></i>
        <span>${theme.hint}</span>
      </span>
    `;

    button.addEventListener("click", () => applyTheme(theme.id));

    item.appendChild(button);
    menu.appendChild(item);
  });

  applyTheme(fallbackTheme);
}

function renderTypeCards(container) {
  container.innerHTML = "";
  const row = document.createElement("div");
  row.className = "row g-3";

  supportedTypes.forEach((type) => {
    const col = document.createElement("div");
    col.className = "col-md-6";
    col.innerHTML = `
      <div class="card h-100 type-card shadow-sm border-0">
        <div class="card-body">
          <div class="d-flex align-items-center gap-3 mb-2">
            <div class="icon-ring bg-${type.tone} bg-opacity-25 text-${type.tone}">
              <i class="bi ${type.icon}"></i>
            </div>
            <div>
              <div class="fw-semibold">${type.title}</div>
              <small class="text-muted">ID: ${type.id}</small>
            </div>
          </div>
          <p class="mb-0 text-muted">${type.description}</p>
        </div>
      </div>
    `;
    row.appendChild(col);
  });

  container.appendChild(row);
}

function getVerdictMeta(verdict) {
  return verdictMeta[verdict] || verdictMeta.неизвестно;
}

function getVerdictGroupMeta(verdicts = []) {
  const hasBlock = verdicts.includes("Блокировка");
  const hasMonitor = verdicts.includes("Мониторинг");

  if (hasBlock && hasMonitor) {
    return { tone: "violet", icon: "bi-shield-exclamation", label: "Блокировка + Мониторинг" };
  }

  return getVerdictMeta(verdicts?.[0]);
}

function formatVerdictLabel(verdicts) {
  const list = verdicts?.length ? verdicts : ["неизвестно"];
  return list.map((verdict) => getVerdictMeta(verdict).label).join(" + ");
}

function getTypeMeta(category) {
  return {
    label: typeTitle.get(category) || category || "Тип не определен",
    tone: typeTone.get(category) || "secondary",
  };
}

function detectHashKind(value) {
  const normalized = String(value || "").trim();

  if (/^[a-fA-F0-9]{32}$/.test(normalized)) {
    return "md5";
  }

  if (/^[a-fA-F0-9]{40}$/.test(normalized)) {
    return "sha1";
  }

  if (/^[a-fA-F0-9]{64}$/.test(normalized)) {
    return "sha256";
  }

  return null;
}

function toggleVerdictFilter(key) {
  if (filterState.hiddenVerdicts.has(key)) {
    filterState.hiddenVerdicts.delete(key);
  } else {
    filterState.hiddenVerdicts.add(key);
  }

  refreshParsedViews();
}

function toggleTypeFilter(category) {
  if (filterState.hiddenTypes.has(category)) {
    filterState.hiddenTypes.delete(category);
  } else {
    filterState.hiddenTypes.add(category);
  }

  refreshParsedViews();
}

function isVerdictVisible(verdicts) {
  const key = iocStore.getGroupKey(iocStore.normalizeVerdicts(verdicts));
  return !filterState.hiddenVerdicts.has(key);
}

function isTypeVisible(category) {
  return !filterState.hiddenTypes.has(category);
}

function getVisibleEntries() {
  return iocStore.entries.filter((entry) => isVerdictVisible(entry.verdicts) && isTypeVisible(entry.category));
}

function buildFilterPill({
  label,
  tone,
  count = null,
  hidden = false,
  onToggle,
  icon = null,
  title = "Нажмите, чтобы скрыть или показать",
}) {
  const pill = document.createElement("span");
  const toneClasses = tone
    ? `bg-${tone} bg-opacity-10 text-${tone} border border-${tone}`
    : "text-bg-light border";
  pill.className = `badge rounded-pill filter-pill ${toneClasses} ${hidden ? "filter-pill-muted" : ""}`;
  pill.setAttribute("role", "button");
  pill.tabIndex = 0;
  pill.title = hidden ? `${title}: скрыто` : `${title}: видно`;
  const iconHtml = icon ? `<i class="bi ${icon}"></i> ` : "";
  const suffix = typeof count === "number" ? `: ${count}` : "";
  pill.innerHTML = `${iconHtml}${label}${suffix}`;

  const handleToggle = () => onToggle && onToggle();

  pill.addEventListener("click", handleToggle);
  pill.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      handleToggle();
    }
  });

  return pill;
}

function buildReasonsBlock(reasons, entry) {
  const reasonTexts = (reasons || [])
    .map((reason) => reason?.text || reason)
    .filter(Boolean);

  if (reasonTexts.length === 0) {
    return null;
  }

  const title = reasonTexts.map((text, index) => `${index + 1}. ${text}`).join("\n");
  const finalReason = reasonTexts[reasonTexts.length - 1];
  const block = document.createElement("div");
  block.className = "text-muted small mt-1 verdict-copy";
  block.title = `${title}\nНажмите, чтобы скопировать фичи анализа`;
  block.textContent = finalReason;

  if (entry) {
    block.setAttribute("role", "button");
    block.tabIndex = 0;
    bindFeaturesCopy(block, entry);
  }

  return block;
}

function buildVirusTotalLink(entry) {
  if (!entry?.value) {
    return null;
  }

  const category = (entry.category || "").toLowerCase();
  const searchValue = category === "url" ? encodeURIComponent(entry.value) : entry.value;

  const link = document.createElement("a");
  link.className = "ioc-vt-link";
  link.href = `https://www.virustotal.com/gui/search/${searchValue}`;
  link.target = "_blank";
  link.rel = "noopener noreferrer";
  link.title = "Открыть индикатор в VirusTotal";

  const icon = document.createElement("img");
  icon.src = "https://www.virustotal.com/gui/images/favicon.svg";
  icon.alt = "VirusTotal";
  icon.width = 16;
  icon.height = 16;
  link.appendChild(icon);

  return link;
}

function buildTagIcons(tags) {
  const normalizedTags = [];
  const seen = new Set();

  (tags || []).forEach((tag) => {
    if (!tag) {
      return;
    }

    const normalized = typeof tag === "string"
      ? { name: tag.trim() }
      : {
          name: String(tag.name || tag.label || tag.title || tag).trim(),
          label: tag.label ? String(tag.label).trim() : undefined,
          title: tag.title ? String(tag.title).trim() : undefined,
          href: tag.icon_url ? String(tag.icon_url) : undefined,
        };

    if (!normalized.name) {
      return;
    }

    const key = normalized.name.toLowerCase();
    if (seen.has(key)) {
      return;
    }

    seen.add(key);
    normalizedTags.push({ key, ...normalized });
  });

  if (!normalizedTags.length) {
    return null;
  }

  const container = document.createElement("span");
  container.className = "ioc-tags";

  normalizedTags.forEach((tag) => {
    const decoration = tagDecorations[tag.key] || null;
    const label = tag.label || decoration?.label || tag.name;
    const title = tag.title || decoration?.title || label;
    const iconSource = decoration?.icon || null;

    if (iconSource) {
      const icon = document.createElement("img");
      icon.src = iconSource;
      icon.alt = label;
      icon.title = title;
      icon.className = "ioc-tag-icon";

      if (tag.href) {
        const link = document.createElement("a");
        link.href = tag.href;
        link.target = "_blank";
        link.rel = "noopener noreferrer";
        link.title = `${title}\nОткрыть карточку тега`;
        link.appendChild(icon);
        container.appendChild(link);
      } else {
        container.appendChild(icon);
      }

      return;
    }

    const badge = document.createElement("span");
    badge.className = "badge bg-light text-dark border ioc-tag-badge";
    badge.textContent = label;
    badge.title = title;
    container.appendChild(badge);
  });

  return container;
}

function setScanState(nextState) {
  Object.assign(scanState, nextState);
  notifyScanStatus();
  refreshParsedViews();
}

async function parseInputText(text) {
  const trimmed = text.trim();
  if (!trimmed) {
    console.warn("[UI] Не передан текст для разбора");
    return false;
  }

  try {
    const response = await apiClient.parseText(trimmed);
    iocStore.loadFromMatches(response.matches);
    setScanState({ error: null });
    console.info("[UI] Выполнен запрос parse/text", { total: iocStore.entries.length });
    refreshParsedViews();
    return true;
  } catch (error) {
    setScanState({ error: error.message });
    console.error("[UI] Не удалось выполнить parse/text", error);
    return false;
  }
}

function collectIndicators() {
  return iocStore.entries.map((entry) => entry.value);
}

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function pollTaskStatus(taskId) {
  // Ожидаем завершения сканирования без ограничения по числу попыток,
  // чтобы не прерывать долгие операции тайм-аутом.
  while (true) {
    try {
      const payload = await apiClient.getTaskStatus(taskId);
      setScanState({ status: payload.status || scanState.status, taskId });
      const verdictPayload = payload.analysis || payload.verdicts;
      if (verdictPayload) {
        iocStore.applyVerdicts(verdictPayload);
        setScanState({ updatedAt: Date.now() });
      }

      if (payload.status === "done" || payload.status === "error") {
        return payload;
      }

      await wait(SCAN_POLL_DELAY);
    } catch (error) {
      console.warn("[UI] Ошибка запроса статуса, повторяем", error);
      await wait(SCAN_POLL_DELAY);
    }
  }
}

async function runScan(text) {
  setScanState({ status: "preparing", error: null, taskId: null });
  const parsed = await parseInputText(text);
  if (!parsed) {
    setScanState({ status: "error", error: scanState.error || "Не удалось разобрать индикаторы" });
    return;
  }

  if (iocStore.entries.length === 0) {
    setScanState({ status: "idle", error: "Не найдены индикаторы для сканирования" });
    return;
  }

  try {
    const response = await apiClient.enqueueTask(text);
    const taskId = response?.task?.id;
    setScanState({ status: "queued", taskId });
    addHistoryEntry(taskId, text);
    console.info("[UI] Задание отправлено на сканирование", { taskId, total: iocStore.entries.length });

    const statusPayload = await pollTaskStatus(taskId);
    const finalStatus = statusPayload.status || "done";
    const verdictPayload = statusPayload.analysis || statusPayload.verdicts;
    if (verdictPayload) {
      iocStore.applyVerdicts(verdictPayload);
    }

    if (finalStatus === "error") {
      setScanState({ status: "error", error: statusPayload.error || "Сканирование завершилось с ошибкой", updatedAt: Date.now() });
    } else {
      setScanState({ status: "done", error: null, updatedAt: Date.now() });
    }
    refreshParsedViews();
  } catch (error) {
    console.error("[UI] Ошибка сканирования", error);
    setScanState({ status: "error", error: error.message });
  }
}

async function resumeLatestHistoryTask() {
  const latest = historyEntries[0];
  if (!latest?.taskId) {
    return;
  }

  try {
    await loadTaskFromHistory(latest);
  } catch (error) {
    console.error("[UI] Ошибка загрузки последней задачи из истории", error);
  }
}

async function loadTaskFromHistory(entry) {
  if (!entry?.taskId) {
    return;
  }

  try {
    setScanState({ status: "queued", taskId: entry.taskId, error: null });
    const payload = await apiClient.getTaskStatus(entry.taskId);
    const payloadStatus = payload.status || "queued";
    setScanState({ status: payloadStatus, taskId: entry.taskId });

    const inputText = payload.input_text || "";
    setInputText(inputText, { silent: true });
    if (inputText.trim()) {
      await parseInputText(inputText);
    } else {
      iocStore.clear();
      refreshParsedViews();
    }

    const verdictPayload = payload.analysis || payload.verdicts;
    if (verdictPayload) {
      iocStore.applyVerdicts(verdictPayload);
      setScanState({ updatedAt: Date.now() });
    }

    refreshParsedViews();
    addHistoryEntry(entry.taskId, inputText, { keepOrder: true });

    if (payloadStatus === "done" || payloadStatus === "error") {
      setScanState({
        status: payloadStatus,
        taskId: entry.taskId,
        error: payload.error || null,
        updatedAt: Date.now(),
      });
      return;
    }

    const finalPayload = await pollTaskStatus(entry.taskId);
    const finalVerdicts = finalPayload.analysis || finalPayload.verdicts;
    if (finalVerdicts) {
      iocStore.applyVerdicts(finalVerdicts);
    }

    setScanState({
      status: finalPayload.status || "done",
      taskId: entry.taskId,
      error: finalPayload.error || null,
      updatedAt: Date.now(),
    });
    refreshParsedViews();
  } catch (error) {
    console.error("[UI] Ошибка загрузки задачи из истории", error);
    setScanState({ status: "error", error: error.message, taskId: entry.taskId });
  }
}

function buildReportText() {
  if (!iocStore.entries.length) {
    return null;
  }

  const rows = ["Индикатор\tТип\tВердикт"]; 
  iocStore.entries.forEach((entry) => {
    const title = typeTitle.get(entry.category) || entry.category || "Неизвестный тип";
    const verdictText = (entry.verdicts || ["неизвестно"]).join(", ");
    rows.push(`${entry.value}\t${title}\t${verdictText}`);
  });

  return rows.join("\n");
}

async function copyReport() {
  const report = buildReportText();
  if (!report) {
    console.warn("[UI] Нет данных для отчета: отправьте текст на разбор");
    showToast({
      title: "Отчет недоступен",
      message: "Добавьте индикаторы и запустите анализ, чтобы сформировать отчет.",
      tone: "warning",
      icon: "bi-clipboard-x",
    });
    return false;
  }

  console.info(`[UI] Текст отчета:\n${report}`);

  try {
    await navigator.clipboard.writeText(report);
    console.info("[UI] Сформирован и скопирован отчет", { total: iocStore.entries.length });
    showToast({
      title: "Отчет скопирован",
      message: `Сводка из ${iocStore.entries.length} строк добавлена в буфер обмена.`,
      tone: "success",
      icon: "bi-clipboard2-check",
    });
    return true;
  } catch (error) {
    console.error("[UI] Ошибка копирования отчета", error);
    showToast({
      title: "Не удалось скопировать отчет",
      message: "Проверьте доступ к буферу обмена и повторите попытку.",
      tone: "danger",
      icon: "bi-clipboard-x",
    });
    return false;
  }
}

function bindIndicatorCopy(element, value) {
  const copyValue = async () => {
    try {
      await navigator.clipboard.writeText(value);
      showToast({
        title: "Скопировано", 
        message: `Индикатор ${value} добавлен в буфер обмена`,
        tone: "success",
        icon: "bi-clipboard-check",
      });
    } catch (error) {
      console.error("[UI] Не удалось скопировать индикатор", error);
    }
  };

  element.addEventListener("click", () => copyValue());
  element.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      copyValue();
    }
  });
}

function bindFeaturesCopy(element, entry) {
  const copyFeatures = async () => {
    const features = entry?.features;
    const hasFeatures = features && Object.keys(features).length > 0;
    if (!hasFeatures) {
      showToast({
        title: "Фичи недоступны",
        message: `Для индикатора ${entry.value} пока нет данных анализа`,
        tone: "warning",
        icon: "bi-clipboard-x",
      });
      return;
    }

    const rawJson = JSON.stringify(features, null, 2);

    try {
      await navigator.clipboard.writeText(rawJson);
      showToast({
        title: "Скопировано",
        message: `Фичи для ${entry.value} добавлены в буфер обмена`,
        tone: "success",
        icon: "bi-clipboard-data",
      });
    } catch (error) {
      console.error("[UI] Не удалось скопировать фичи", error);
    }
  };

  element.addEventListener("click", () => copyFeatures());
  element.addEventListener("keydown", (event) => {
    if (event.key === "Enter" || event.key === " ") {
      event.preventDefault();
      copyFeatures();
    }
  });
}

function buildVerdictHeader(verdicts, total) {
  const list = verdicts?.length ? verdicts : ["неизвестно"];
  const leadMeta = getVerdictGroupMeta(list);
  const badges = list
    .map((verdict) => {
      const meta = getVerdictMeta(verdict);
      return `<span class="badge rounded-pill bg-${meta.tone} bg-opacity-10 text-${meta.tone} border border-${meta.tone}">${meta.label}</span>`;
    })
    .join("");

  return `
    <div class="d-flex align-items-center gap-2 text-${leadMeta.tone} verdict-header flex-wrap">
      <i class="bi ${leadMeta.icon}"></i>
      <div class="d-flex flex-wrap gap-1">${badges}</div>
      <span class="badge text-bg-light">${total}</span>
    </div>
  `;
}

function renderParsedContent(target) {
  target.innerHTML = "";

  if (!iocStore.entries.length) {
    const placeholder = document.createElement("div");
    placeholder.className = "alert alert-light border mb-0";
    placeholder.textContent = "После сканирования здесь появятся индикаторы с вердиктами.";
    target.appendChild(placeholder);
    return;
  }

  const visibleEntries = getVisibleEntries();
  const groupedVisible = iocStore.groupByVerdict(visibleEntries);

  const summaryBlock = document.createElement("div");
  summaryBlock.className = "d-flex flex-column gap-2 mb-3";

  const summaryRow = document.createElement("div");
  summaryRow.className = "d-flex flex-wrap align-items-center gap-2";
  const totalBadge = document.createElement("span");
  totalBadge.className = "badge text-bg-primary";
  totalBadge.textContent = `Всего: ${visibleEntries.length}`;
  summaryRow.appendChild(totalBadge);

  if (visibleEntries.length !== iocStore.entries.length) {
    const filteredNote = document.createElement("span");
    filteredNote.className = "badge text-bg-light text-muted border";
    filteredNote.textContent = `Скрыто фильтрами: ${iocStore.entries.length - visibleEntries.length}`;
    summaryRow.appendChild(filteredNote);
  }

  if (scanState.updatedAt) {
    const updatedLabel = document.createElement("span");
    updatedLabel.className = "text-muted small";
    updatedLabel.textContent = `Обновлено: ${new Date(scanState.updatedAt).toLocaleTimeString()}`;
    summaryRow.appendChild(updatedLabel);
  }

  summaryBlock.appendChild(summaryRow);

  const verdictFilterRow = document.createElement("div");
  verdictFilterRow.className = "d-flex flex-wrap align-items-center gap-2 filter-row";
  const verdictLabel = document.createElement("span");
  verdictLabel.className = "text-muted small";
  verdictLabel.textContent = "Категории:";
  verdictFilterRow.appendChild(verdictLabel);

  iocStore.groupByVerdict().forEach((group) => {
    const key = iocStore.getGroupKey(group.verdicts);
    const meta = getVerdictGroupMeta(group.verdicts);
    const hidden = filterState.hiddenVerdicts.has(key);
    const pill = buildFilterPill({
      label: formatVerdictLabel(group.verdicts),
      tone: meta.tone,
      count: group.entries.length,
      hidden,
      onToggle: () => toggleVerdictFilter(key),
      icon: meta.icon,
      title: "Фильтр по вердикту",
    });
    verdictFilterRow.appendChild(pill);
  });

  summaryBlock.appendChild(verdictFilterRow);

  const typeFilterRow = document.createElement("div");
  typeFilterRow.className = "d-flex flex-wrap align-items-center gap-2 filter-row";
  const typeLabel = document.createElement("span");
  typeLabel.className = "text-muted small";
  typeLabel.textContent = "Типы:";
  typeFilterRow.appendChild(typeLabel);

  const typeCount = iocStore.entries.reduce((acc, entry) => {
    const key = entry.category || "unknown";
    acc[key] = (acc[key] || 0) + 1;
    return acc;
  }, {});

  Object.entries(typeCount)
    .sort(([, leftCount], [, rightCount]) => rightCount - leftCount)
    .forEach(([category, count]) => {
      const meta = getTypeMeta(category);
      const hidden = filterState.hiddenTypes.has(category);
      const pill = buildFilterPill({
        label: meta.label,
        tone: meta.tone,
        count,
        hidden,
        onToggle: () => toggleTypeFilter(category),
        icon: "bi-sliders",
        title: "Фильтр по типу индикатора",
      });
      typeFilterRow.appendChild(pill);
    });

  summaryBlock.appendChild(typeFilterRow);

  target.appendChild(summaryBlock);

  if (!visibleEntries.length) {
    const empty = document.createElement("div");
    empty.className = "alert alert-secondary border";
    empty.textContent = "Все индикаторы скрыты фильтрами. Снимите часть фильтров, чтобы увидеть результаты.";
    target.appendChild(empty);
    return;
  }

  groupedVisible.forEach((group) => {
    const { verdicts, entries } = group;
    const card = document.createElement("div");
    card.className = "card shadow-sm border-0 mb-3";

    const body = document.createElement("div");
    body.className = "card-body pb-2";
    body.innerHTML = buildVerdictHeader(verdicts, entries.length);

    const list = document.createElement("ul");
    list.className = "list-group list-group-flush parsed-values";

    entries.forEach((entry) => {
      const badgeTitle = typeTitle.get(entry.category) || entry.category || "Тип не определен";
      const item = document.createElement("li");
      item.className = "list-group-item px-0 parsed-item";

      const wrapper = document.createElement("div");
      wrapper.className = "d-flex flex-column gap-1";

      const line = document.createElement("div");
      line.className = "parsed-line";

      const value = document.createElement("span");
      value.className = "ioc-value font-monospace ioc-value-copyable";
      value.title = entry.value;
      value.textContent = entry.value;
      value.setAttribute("role", "button");
      value.tabIndex = 0;
      bindIndicatorCopy(value, entry.value);

      const tags = buildTagIcons(entry.tags);

      const vtLink = buildVirusTotalLink(entry);

      const badge = document.createElement("span");
      const hashKind = entry.category === "hash" ? detectHashKind(entry.value) : null;
      badge.className = "badge ioc-type-badge";
      badge.textContent = badgeTitle;

      if (hashKind) {
        badge.classList.add("hash-badge", `hash-${hashKind}`);
        badge.textContent = `${badgeTitle} · ${hashKind.toUpperCase()}`;
      }

      if (tags) {
        line.appendChild(tags);
      }
      line.appendChild(value);
      if (vtLink) {
        line.appendChild(vtLink);
      }
      line.appendChild(badge);
      wrapper.appendChild(line);

      const reasons = buildReasonsBlock(entry.reasons, entry);
      if (reasons) {
        wrapper.appendChild(reasons);
      }

      item.appendChild(wrapper);
      list.appendChild(item);
    });

    body.appendChild(list);
    card.appendChild(body);
    target.appendChild(card);
  });
}

function refreshParsedViews() {
  parsedViews.forEach((view) => renderParsedContent(view));
}

function renderHealthWidget(container, { instanceId }) {
  container.classList.add("health-widget");

  const status = document.createElement("div");
  status.className = "d-flex align-items-center gap-2 api-status-panel";
  status.innerHTML = `
    <span class="api-status-dot rounded-circle bg-secondary" aria-hidden="true"></span>
    <div class="d-flex flex-column lh-sm">
      <span class="api-status-title fw-semibold text-secondary">API</span>
      <small class="api-status-note text-muted">Ожидаем запрос...</small>
      <small class="api-status-time text-muted">Обновлено: —</small>
    </div>
  `;

  container.appendChild(status);
  registerHealthTarget(instanceId, status);
}

function renderHistoryWidget(container, { instanceId }) {
  container.classList.add("history-widget");

  const listHolder = document.createElement("div");
  listHolder.className = "history-list-holder";

  container.appendChild(listHolder);
  historyViews.set(instanceId, listHolder);
  renderHistoryContent(listHolder);
}

function renderInputWidget(container) {
  const textarea = document.createElement("textarea");
  textarea.className = "form-control ioc-input";
  textarea.rows = 8;
  textarea.placeholder = "Вставьте IP, домены, URL или хеши";
  let lastSubmittedText = "";
  let scanTimeoutId = null;
  let isScanning = false;

  inputBindings.add(textarea);

  container.classList.add("input-widget-body");

  const controls = document.createElement("div");
  controls.className = "d-flex flex-wrap gap-2 mt-3 align-items-center input-widget-controls";

  const scanBtn = document.createElement("button");
  scanBtn.className = "btn btn-success";
  scanBtn.innerHTML = `<i class="bi bi-search"></i> Сканировать`;

  const reportBtn = document.createElement("button");
  reportBtn.className = "btn btn-outline-primary";
  reportBtn.innerHTML = `<i class="bi bi-clipboard2-check"></i> Отчет`;

  controls.appendChild(scanBtn);
  controls.appendChild(reportBtn);

  container.appendChild(textarea);
  container.appendChild(controls);

  const scheduleAutoScan = () => {
    clearTimeout(scanTimeoutId);
    scanTimeoutId = setTimeout(async () => {
      const trimmed = textarea.value.trim();
      if (!trimmed || trimmed === lastSubmittedText) {
        return;
      }

      const success = await parseInputText(textarea.value);
      if (success) {
        lastSubmittedText = trimmed;
      }
    }, 500);
  };

  textarea.addEventListener("input", () => {
    scheduleAutoScan();
  });
  scanBtn.addEventListener("click", async () => {
    if (isScanning) {
      return;
    }

    isScanning = true;
    const originalLabel = scanBtn.innerHTML;
    scanBtn.disabled = true;
    scanBtn.innerHTML = `<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Сканируем`;

    await runScan(textarea.value);
    lastSubmittedText = textarea.value.trim();

    scanBtn.disabled = false;
    scanBtn.innerHTML = originalLabel;
    isScanning = false;
  });
  reportBtn.addEventListener("click", async () => copyReport());
}

function renderParsedWidget(container) {
  const listHolder = document.createElement("div");
  listHolder.className = "parsed-list";

  container.appendChild(listHolder);
  parsedViews.add(listHolder);
  refreshParsedViews();
}

function renderTypesWidget(container) {
  const intro = document.createElement("p");
  intro.className = "text-muted";
  intro.textContent = "Карточки можно перетаскивать, чтобы выстроить удобный обзор.";

  const typesHolder = document.createElement("div");
  renderTypeCards(typesHolder);

  container.appendChild(intro);
  container.appendChild(typesHolder);
}

function buildTemplates() {
  return [
    {
      typeId: "health",
      title: "Статус API",
      icon: "bi-activity",
      w: 6,
      h: 6,
      minW: 3,
      minH: 6,
      render: renderHealthWidget,
    },
    {
      typeId: "history",
      title: "История",
      icon: "bi-clock-history",
      w: 5,
      h: 10,
      minW: 3,
      minH: 6,
      render: renderHistoryWidget,
    },
    {
      typeId: "input",
      title: "Ввод индикаторов",
      icon: "bi-pencil-square",
      w: 14,
      h: 12,
      render: renderInputWidget,
    },
    {
      typeId: "parsed",
      title: "Результаты анализа",
      icon: "bi-diagram-3",
      w: 10,
      h: 10,
      render: renderParsedWidget,
    },
    {
      typeId: "types",
      title: "Поддерживаемые типы",
      icon: "bi-grid-3x3-gap",
      w: 12,
      h: 10,
      render: renderTypesWidget,
    },
  ];
}

function buildTemplateMenu(templates, manager) {
  const menu = document.getElementById("widget-template-menu");
  menu.innerHTML = "";

  templates.forEach((template) => {
    const item = document.createElement("li");
    const button = document.createElement("button");
    button.type = "button";
    button.className = "dropdown-item d-flex align-items-center gap-2";
    button.dataset.templateId = template.typeId;
    button.innerHTML = `<i class="bi ${template.icon} text-primary"></i><span>${template.title}</span>`;

    button.addEventListener("click", () => {
      manager.restoreHidden(template.typeId);
      manager.addWidget(template.typeId);
    });

    item.appendChild(button);
    menu.appendChild(item);
  });
}

function bootstrapWidgets() {
  const gridElement = document.getElementById("widgets-grid");
  const templates = buildTemplates();
  const manager = new WidgetManager(gridElement, {
    onWidgetRemoved: (typeId, instanceId) => {
      if (typeId === "health") {
        unregisterHealthTarget(instanceId);
      } else if (typeId === "history") {
        unregisterHistoryView(instanceId);
      }
    },
  });
  templates.forEach((template) => manager.registerTemplate(template));

  manager.initGrid();
  buildTemplateMenu(templates, manager);
}

document.addEventListener("DOMContentLoaded", () => {
  restoreHistoryFromStorage();
  buildThemeMenu();
  bootstrapWidgets();
  renderHistoryViews();
  refreshParsedViews();
  resumeLatestHistoryTask();
});
