(() => {
  const { useState, useEffect, useRef, useCallback } = React;
  const DEFAULT_DATE_TIME_CONFIG = {
    locale: "es-PE",
    time_zone: "America/Lima",
  };
  const dateTimeConfig = { ...DEFAULT_DATE_TIME_CONFIG };

  const getToken = () => localStorage.getItem("dc_token");

  // Decodifica el payload del JWT sin verificar (solo para leer `exp`/`iat`).
  const decodeJwtPayload = (token) => {
    try {
      const part = String(token || "").split(".")[1];
      if (!part) return null;
      const padded = part.replace(/-/g, "+").replace(/_/g, "/");
      const json = atob(padded + "===".slice((padded.length + 3) % 4));
      return JSON.parse(json);
    } catch (_error) {
      return null;
    }
  };

  // Renueva proactivamente cuando faltan menos de 5 min para que expire.
  const PROACTIVE_REFRESH_WINDOW_MS = 5 * 60 * 1000;

  const isAccessTokenNearExpiry = () => {
    const token = getToken();
    if (!token) return false;
    const payload = decodeJwtPayload(token);
    if (!payload?.exp) return false;
    const expiresAtMs = payload.exp * 1000;
    return expiresAtMs - Date.now() < PROACTIVE_REFRESH_WINDOW_MS;
  };

  let _refreshInProgress = null;

  const tryRefreshToken = async () => {
    if (_refreshInProgress) return _refreshInProgress;
    _refreshInProgress = fetch("/api/auth/refresh", {
      method: "POST",
      credentials: "include",
      headers: {
        Authorization: `Bearer ${getToken() || ""}`,
      },
    })
      .then(async (r) => {
        if (!r.ok) return null;
        const data = await r.json();
        if (data?.token) {
          localStorage.setItem("dc_token", data.token);
          return data.token;
        }
        return null;
      })
      .catch(() => null)
      .finally(() => { _refreshInProgress = null; });
    return _refreshInProgress;
  };

  const ensureFreshAccessToken = async () => {
    if (!getToken()) return null;
    if (!isAccessTokenNearExpiry()) return getToken();
    return tryRefreshToken();
  };

  const apiFetch = async (path, opts = {}, _retry = true) => {
    if (_retry) await ensureFreshAccessToken();

    const token = getToken();
    const response = await fetch(`/api${path}`, {
      headers: {
        "Content-Type": "application/json",
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      },
      credentials: "include",
      ...opts,
      body: opts.body ? JSON.stringify(opts.body) : undefined,
    });

    if (response.status === 401 && _retry) {
      const newToken = await tryRefreshToken();
      if (newToken) return apiFetch(path, opts, false);
      localStorage.removeItem("dc_token");
      window.location.reload();
      return null;
    }

    if (response.status === 401) {
      localStorage.removeItem("dc_token");
      window.location.reload();
      return null;
    }

    return response.json();
  };

  // Refresco activo: cada 4 min mientras la pestaña esté visible, y al
  // recuperar el foco si falta menos del umbral para expirar.
  const SLIDING_REFRESH_INTERVAL_MS = 4 * 60 * 1000;
  if (typeof window !== "undefined") {
    setInterval(() => {
      if (typeof document !== "undefined" && document.hidden) return;
      ensureFreshAccessToken();
    }, SLIDING_REFRESH_INTERVAL_MS);

    if (typeof document !== "undefined") {
      document.addEventListener("visibilitychange", () => {
        if (!document.hidden) ensureFreshAccessToken();
      });
    }
    window.addEventListener("focus", () => { ensureFreshAccessToken(); });
  }

  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  const setDateTimeConfig = (nextConfig = {}) => {
    dateTimeConfig.locale = String(nextConfig.locale || DEFAULT_DATE_TIME_CONFIG.locale).trim() || DEFAULT_DATE_TIME_CONFIG.locale;
    dateTimeConfig.time_zone = String(nextConfig.time_zone || DEFAULT_DATE_TIME_CONFIG.time_zone).trim() || DEFAULT_DATE_TIME_CONFIG.time_zone;
    if (typeof window !== "undefined") {
      window.dispatchEvent(new CustomEvent("dc:datetime-config-updated", { detail: { ...dateTimeConfig } }));
    }
  };
  const getDateTimeConfig = () => ({ ...dateTimeConfig });
  // SQLite datetime('now') devuelve "YYYY-MM-DD HH:MM:SS[.SSS]" en UTC sin sufijo Z.
  // JS interpreta strings sin TZ como hora local: añadimos 'Z' para evitar el shift.
  const SQLITE_DATETIME_RE = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(\.\d+)?$/;
  const normalizeDateInput = (value) => {
    if (typeof value !== "string") return value;
    return SQLITE_DATETIME_RE.test(value) ? `${value.replace(" ", "T")}Z` : value;
  };
  const formatDateTime = (value, options, fallback = "—") =>
    value
      ? new Date(normalizeDateInput(value)).toLocaleString(dateTimeConfig.locale, {
          timeZone: dateTimeConfig.time_zone,
          ...options,
        })
      : fallback;
  const escapeHtml = (value) =>
    String(value ?? "").replace(/[&<>"']/g, (char) => ({
      "&": "&amp;",
      "<": "&lt;",
      ">": "&gt;",
      '"': "&quot;",
      "'": "&#39;",
    }[char] || char));
  const fmtDate = (value) =>
    formatDateTime(value, {
      day: "2-digit",
      month: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
    });
  const fmtFullDate = (value) =>
    formatDateTime(value, {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit",
    });

  const buildSafePdfFilename = (value) => {
    const normalized = String(value || "reporte")
      .normalize("NFD")
      .replace(/[\u0300-\u036f]/g, "")
      .replace(/[^a-zA-Z0-9._-]+/g, "-")
      .replace(/-+/g, "-")
      .replace(/^-|-$/g, "")
      .toLowerCase();
    return normalized || "reporte";
  };

  const reportStyles = `
    :root {
      color-scheme: light;
      --ink: #0f172a;
      --muted: #475569;
      --line: #cbd5e1;
      --bg-soft: #f8fafc;
      --ok: #166534;
      --warn: #92400e;
      --err: #991b1b;
      --info: #0f766e;
    }
    * { box-sizing: border-box; }
    .dc-pdf-root {
      width: 100%;
      min-height: 100%;
      padding: 28px;
      font-family: Inter, "Segoe UI", sans-serif;
      color: var(--ink);
      background: white;
    }
    h1, h2, h3, p { margin: 0; }
    .report-head {
      margin-bottom: 20px;
      padding-bottom: 14px;
      border-bottom: 2px solid var(--line);
    }
    .report-title {
      font-size: 28px;
      font-weight: 800;
      letter-spacing: -.02em;
    }
    .report-subtitle {
      margin-top: 6px;
      color: var(--muted);
      font-size: 13px;
    }
    .report-section {
      margin-top: 18px;
      break-inside: avoid-page;
      page-break-inside: avoid;
    }
    .report-section h2 {
      font-size: 15px;
      font-weight: 800;
      margin-bottom: 10px;
      text-transform: uppercase;
      letter-spacing: .08em;
      color: var(--muted);
    }
    .metrics {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
      gap: 10px;
    }
    .metric {
      border: 1px solid var(--line);
      border-radius: 12px;
      padding: 12px;
      background: var(--bg-soft);
    }
    .metric-label {
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: .08em;
      color: var(--muted);
    }
    .metric-value {
      margin-top: 6px;
      font-size: 20px;
      font-weight: 800;
    }
    .meta-list {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      gap: 8px 14px;
    }
    .meta-item {
      font-size: 13px;
      color: var(--muted);
    }
    .meta-item strong {
      color: var(--ink);
    }
    table {
      width: 100%;
      border-collapse: collapse;
      font-size: 12px;
    }
    th, td {
      border: 1px solid var(--line);
      padding: 8px 10px;
      vertical-align: top;
      text-align: left;
    }
    th {
      background: var(--bg-soft);
      font-size: 11px;
      text-transform: uppercase;
      letter-spacing: .08em;
    }
    .status-ok { color: var(--ok); font-weight: 700; }
    .status-partial, .status-timeout, .status-warn, .status-skipped { color: var(--warn); font-weight: 700; }
    .status-error, .status-http_error, .status-failed { color: var(--err); font-weight: 700; }
    .status-running, .status-info { color: var(--info); font-weight: 700; }
    .muted { color: var(--muted); }
    .mono {
      font-family: "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;
      font-size: 11px;
      word-break: break-word;
    }
    .pre {
      white-space: pre-wrap;
      border: 1px solid var(--line);
      border-radius: 12px;
      padding: 12px;
      background: var(--bg-soft);
    }
    .callout {
      border: 1px solid var(--line);
      border-left: 4px solid #0ea5e9;
      padding: 12px 14px;
      border-radius: 12px;
      background: #f8fdff;
      color: var(--muted);
    }
  `;

  // Alternativa a openPrintReport para documentos grandes (sin límite de canvas).
  // Abre una ventana nueva y usa window.print() del navegador.
  const openPrintWindow = ({ title, subtitle = "", bodyHtml = "" }) => {
    const win = window.open("", "_blank");
    if (!win) return false;
    win.document.write(`<!DOCTYPE html><html lang="es"><head>
      <meta charset="utf-8">
      <title>${escapeHtml(title)}</title>
      <style>
        ${reportStyles}
        @media print {
          body { margin: 0; }
          .dc-pdf-root { padding: 14px; }
          table { page-break-inside: auto; }
          tr { page-break-inside: avoid; page-break-after: auto; }
          .report-section { page-break-inside: avoid; }
        }
      </style>
    </head><body>
      <div class="dc-pdf-root">
        <header class="report-head">
          <div class="report-title">${escapeHtml(title)}</div>
          <div class="report-subtitle">
            ${escapeHtml(subtitle)}${subtitle ? " · " : ""}Generado ${escapeHtml(fmtFullDate(new Date().toISOString()))}
          </div>
        </header>
        ${bodyHtml}
      </div>
    </body></html>`);
    win.document.close();
    // Esperar a que el documento cargue antes de imprimir
    win.addEventListener("load", () => win.print());
    // Fallback si el evento load ya disparó
    if (win.document.readyState === "complete") win.print();
    return true;
  };

  // ── Shared jsPDF helpers ──────────────────────────────────────────
  const PDF_C = {
    ok:   [22, 101, 52],
    err:  [153, 27, 27],
    skip: [120, 113, 108],
    warn: [146, 64, 14],
    head: [15, 23, 42],
    muted:[71, 85, 105],
    line: [203, 213, 225],
    bg:   [248, 250, 252],
  };

  const pdfCreateDoc = ({ title, subtitle = "", landscape = false }) => {
    const { jspdf } = window;
    if (!jspdf?.jsPDF) return null;
    const doc = new jspdf.jsPDF({ orientation: landscape ? "landscape" : "portrait", unit: "mm", format: "a4" });
    const pageW = doc.internal.pageSize.getWidth();
    // Dark header band
    doc.setFillColor(...PDF_C.head);
    doc.rect(0, 0, pageW, 30, "F");
    doc.setFont("helvetica", "bold");
    doc.setFontSize(15);
    doc.setTextColor(255, 255, 255);
    doc.text(title.slice(0, 90), 12, 12);
    if (subtitle) {
      doc.setFontSize(8);
      doc.setFont("helvetica", "normal");
      doc.setTextColor(148, 163, 184);
      doc.text(subtitle.slice(0, 140), 12, 19);
    }
    doc.setFontSize(7);
    doc.setTextColor(100, 116, 139);
    doc.text(`Generado ${fmtFullDate(new Date().toISOString())}`, 12, 26);
    return { doc, pageW, startY: 34 };
  };

  // metrics: array of { label, value, color? }
  const pdfAddMetrics = ({ doc, pageW, startY, metrics }) => {
    const mw = (pageW - 24) / metrics.length;
    metrics.forEach(({ label, value, color }, idx) => {
      const mx = 12 + idx * mw;
      doc.setFillColor(...PDF_C.bg);
      doc.setDrawColor(...PDF_C.line);
      doc.roundedRect(mx, startY, mw - 3, 19, 2, 2, "FD");
      doc.setFontSize(6.5);
      doc.setFont("helvetica", "normal");
      doc.setTextColor(...PDF_C.muted);
      doc.text(String(label).toUpperCase(), mx + 3, startY + 6);
      doc.setFontSize(13);
      doc.setFont("helvetica", "bold");
      doc.setTextColor(...(color || PDF_C.head));
      doc.text(String(value ?? "—"), mx + 3, startY + 15);
    });
    return startY + 23;
  };

  // key-value info grid
  const pdfAddInfo = ({ doc, pageW, startY, rows }) => {
    const cols = 2;
    const cw = (pageW - 24) / cols;
    rows.forEach(([k, v], i) => {
      const col = i % cols;
      const row = Math.floor(i / cols);
      const x = 12 + col * cw;
      const y = startY + row * 7 + 5;
      doc.setFontSize(7);
      doc.setFont("helvetica", "normal");
      doc.setTextColor(...PDF_C.muted);
      doc.text(`${k}: `, x, y);
      doc.setFont("helvetica", "bold");
      doc.setTextColor(...PDF_C.head);
      const kw = doc.getTextWidth(`${k}: `);
      doc.text(String(v ?? "—").slice(0, 60), x + kw, y);
    });
    const totalRows = Math.ceil(rows.length / cols);
    return startY + totalRows * 7 + 6;
  };

  const pdfSectionTitle = ({ doc, pageW, startY, title }) => {
    doc.setFont("helvetica", "bold");
    doc.setFontSize(8);
    doc.setTextColor(...PDF_C.muted);
    doc.text(title.toUpperCase(), 12, startY + 5);
    doc.setDrawColor(...PDF_C.line);
    doc.line(12, startY + 7, pageW - 12, startY + 7);
    return startY + 11;
  };

  const pdfAddFooters = ({ doc, title }) => {
    const total = doc.internal.getNumberOfPages();
    for (let p = 1; p <= total; p++) {
      doc.setPage(p);
      doc.setFontSize(7);
      doc.setTextColor(...PDF_C.muted);
      doc.text(
        `Página ${p} / ${total}  ·  ${title.slice(0, 80)}`,
        12,
        doc.internal.pageSize.getHeight() - 5
      );
    }
  };

  // Color for status text cells
  const pdfStatusColor = (status) => {
    if (status === "ok") return PDF_C.ok;
    if (["error","http_error","timeout","failed"].includes(status)) return PDF_C.err;
    if (status === "skipped") return PDF_C.skip;
    if (status === "partial") return PDF_C.warn;
    return PDF_C.muted;
  };

  const pdfAutoTableDefaults = (doc) => ({
    styles: { fontSize: 7, cellPadding: 2, overflow: "linebreak" },
    headStyles: { fillColor: PDF_C.head, textColor: [255,255,255], fontStyle: "bold", fontSize: 7 },
    alternateRowStyles: { fillColor: [249, 250, 251] },
    margin: { left: 12, right: 12 },
    theme: "grid",
    didParseCell(data) {
      // color the status column (index 2 by default)
      if (data.section === "body" && data.column.dataKey === "__status__") {
        data.cell.styles.textColor = pdfStatusColor(String(data.cell.raw || ""));
        data.cell.styles.fontStyle = "bold";
      }
    },
  });

  const openPrintReport = async ({ title, subtitle = "", bodyHtml = "", filename = "" }) => {
    if (!window.html2pdf) {
      console.error("html2pdf no está disponible en window.");
      return false;
    }

    const mount = document.createElement("div");
    mount.setAttribute("aria-hidden", "true");
    mount.style.position = "fixed";
    mount.style.left = "-200vw";
    mount.style.top = "0";
    mount.style.width = "1024px";
    mount.style.pointerEvents = "none";
    mount.style.opacity = "0";
    mount.innerHTML = `
      <style>${reportStyles}</style>
      <div class="dc-pdf-root">
        <header class="report-head">
          <div class="report-title">${escapeHtml(title)}</div>
          <div class="report-subtitle">
            ${escapeHtml(subtitle)}
            ${subtitle ? " · " : ""}
            Generado ${escapeHtml(fmtFullDate(new Date().toISOString()))}
          </div>
        </header>
        ${bodyHtml}
      </div>
    `;

    document.body.appendChild(mount);
    const reportNode = mount.querySelector(".dc-pdf-root");
    const pdfName = `${buildSafePdfFilename(filename || title || "reporte")}.pdf`;

    try {
      await window.html2pdf()
        .set({
          filename: pdfName,
          margin: [10, 10, 12, 10],
          image: { type: "jpeg", quality: 0.98 },
          html2canvas: {
            scale: 2,
            useCORS: true,
            backgroundColor: "#ffffff",
          },
          jsPDF: {
            unit: "mm",
            format: "a4",
            orientation: "portrait",
          },
          pagebreak: {
            mode: ["css", "legacy"],
          },
        })
        .from(reportNode)
        .save();

      return true;
    } catch (error) {
      console.error("No se pudo exportar el PDF", error);
      return false;
    } finally {
      mount.remove();
    }
  };

  // Builders centralizados para los keys que viven en estado runningAction
  // del NOC/OPS. Un solo lugar evita bugs por concatenación divergente.
  const runKey = {
    incident: (action, id) => `${action}:${id}`,
    runbook: (action, serverId) => `runbook:${action}:${serverId}`,
    scheduler: (action) => String(action),
    simple: (action) => String(action),
  };

  const SC = {
    ok: "#22d3a5",
    error: "#f43f5e",
    skipped: "#fbbf24",
    excluded: "#64748b",
    no_git: "#a78bfa",
    running: "#00e5ff",
    success: "#22d3a5",
    partial: "#fbbf24",
    failed: "#f43f5e",
    rolled_back: "#a78bfa",
    timeout: "#fbbf24",
    pending: "#475569",
    stopped: "#64748b",
  };

  const SI = {
    ok: "✅",
    error: "❌",
    timeout: "⏱",
    skipped: "⏭",
    pending: "⏸",
    running: "⟳",
    stopped: "⏹",
    partial: "⚠",
    rolled_back: "↩",
  };

  // Etiquetas legibles para las filas "sentinel" de deploy_results (virt_user que
  // empieza con "_" no es un cliente, es una fila de sistema).
  const SENTINEL_LABEL = {
    _git_pull_: "Git pull",
    _server_: "Servidor",
    _lock_: "Lock",
    _validacion_: "Validación pre-deploy",
    _val_php_: "Validación: PHP lint",
    _val_mig_dup_: "Validación: clase de migración duplicada",
    _val_mig_num_: "Validación: numeración de migración",
    _val_ai_: "Validación: revisión IA",
  };

  // Devuelve la etiqueta legible si es un sentinel; si no, el virt_user tal cual.
  const vuLabel = (virtUser) => SENTINEL_LABEL[virtUser] || virtUser || "—";

  const ROLE_LABEL = {
    superadmin: "Super Admin",
    admin: "Admin",
    viewer: "Viewer",
  };

  const ROLE_COLOR = {
    superadmin: "#f43f5e",
    admin: "#00e5ff",
    viewer: "#64748b",
  };

  const hardenFormAutofill = (element) => {
    if (!(element instanceof HTMLElement)) return;

    const ignoredTypes = new Set(["hidden", "checkbox", "radio", "submit", "button", "file"]);
    const allowAutocomplete = (node) =>
      node?.closest?.("[data-allow-autocomplete='true']") instanceof HTMLElement;
    const markForm = (form) => {
      if (allowAutocomplete(form)) return;
      form.setAttribute("autocomplete", "off");
      form.setAttribute("data-lpignore", "true");
      form.setAttribute("data-1p-ignore", "true");
      form.setAttribute("data-bwignore", "true");
    };
    const markField = (field) => {
      if (allowAutocomplete(field)) return;
      const type = String(field.getAttribute("type") || "text").toLowerCase();
      if (ignoredTypes.has(type)) return;
      field.setAttribute("autocomplete", type === "password" ? "new-password" : "off");
      field.setAttribute("autocorrect", "off");
      field.setAttribute("autocapitalize", "none");
      field.setAttribute("spellcheck", "false");
      field.setAttribute("data-lpignore", "true");
      field.setAttribute("data-1p-ignore", "true");
      field.setAttribute("data-bwignore", "true");
    };

    if (element.matches("form")) markForm(element);
    if (element.matches("input, textarea")) markField(element);

    element.querySelectorAll("form").forEach(markForm);
    element.querySelectorAll("input, textarea").forEach(markField);
  };

  const initFormAutofillHardening = () => {
    if (!document.body) return;

    hardenFormAutofill(document.body);

    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        mutation.addedNodes.forEach((node) => hardenFormAutofill(node));
      });
    });

    observer.observe(document.body, { childList: true, subtree: true });
  };

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initFormAutofillHardening, { once: true });
  } else {
    initFormAutofillHardening();
  }

  window.DC = {
    React,
    ReactDOM,
    useState,
    useEffect,
    useRef,
    useCallback,
    getToken,
    apiFetch,
    sleep,
    escapeHtml,
    fmtDate,
    fmtFullDate,
    formatDateTime,
    setDateTimeConfig,
    getDateTimeConfig,
    openPrintReport,
    openPrintWindow,
    pdfColors: PDF_C,
    pdfCreateDoc,
    pdfAddMetrics,
    pdfAddInfo,
    pdfSectionTitle,
    pdfAddFooters,
    pdfStatusColor,
    pdfAutoTableDefaults,
    SC,
    SI,
    SENTINEL_LABEL,
    vuLabel,
    ROLE_LABEL,
    ROLE_COLOR,
    runKey,
  };

  window.DCComponents = window.DCComponents || {};
  window.DCViews = window.DCViews || {};
})();
