(() => {
  const { SC, runKey } = window.DC;

  const PLAYBOOK_CATEGORIES = {
    server: { label: "Servidor", tone: "#f43f5e", icon: "🖥" },
    services: { label: "Servicios", tone: "#fbbf24", icon: "🧩" },
    resources: { label: "Recursos", tone: "#a78bfa", icon: "📊" },
    network: { label: "Red / DNS", tone: "#22d3ee", icon: "🌐" },
    sites: { label: "Sitios", tone: "#22d3a5", icon: "🌍" },
    deploy: { label: "Deploy", tone: "#f472b6", icon: "🚀" },
  };

  const PLAYBOOK_META = {
    server_unreachable: { title: "Servidor inaccesible", category: "server" },
    service_apache_inactive: { title: "Apache/Nginx caído", category: "services" },
    service_php_fpm_inactive: { title: "PHP-FPM caído", category: "services" },
    service_mariadb_inactive: { title: "MariaDB/MySQL caído", category: "services" },
    resource_cpu_critical: { title: "CPU al 100%", category: "resources" },
    resource_ram_critical: { title: "RAM saturada", category: "resources" },
    resource_disk_critical: { title: "Disco lleno", category: "resources" },
    resource_pressure_warning: { title: "Presión moderada", category: "resources" },
    dns_degraded: { title: "DNS del servidor degradado", category: "network" },
    site_down: { title: "Sitio caído (5xx / timeout)", category: "sites" },
    site_slow: { title: "Sitio lento", category: "sites" },
    site_dns_fail: { title: "Dominio sin DNS", category: "sites" },
    site_ssl_expiring: { title: "Certificado SSL por vencer", category: "sites" },
    deploy_failure: { title: "Deploy fallido", category: "deploy" },
    deploy_rollback: { title: "Deploy revertido automáticamente", category: "deploy" },
    deploy_validation_failure: { title: "Validación post-deploy degradada", category: "deploy" },
  };

  const PLAYBOOKS = {
    server_unreachable: {
      diagnosis: "El monitor no logra conectar por SSH. El servidor podría estar apagado, sin red, con SSH caído o con credenciales rotadas.",
      causes: [
        "El host está apagado, reiniciando o sin red",
        "El servicio sshd no está activo",
        "Cambió la IP, usuario o llave configurada",
        "Firewall bloqueando el puerto 22 desde este host",
      ],
      recommendedActions: [],
      manualSteps: [
        "Verifica acceso SSH manual desde otra terminal",
        "Confirma con el cliente o proveedor si hay reinicio o mantenimiento",
        "Revisa la consola/KVM del proveedor si no responde a ping",
        "Si recuperó conectividad, vuelve a barrer métricas para cerrar el incidente",
      ],
      verifyAfter: "Cuando vuelva a responder, ejecuta 'Releer métricas' para confirmar y cerrar automáticamente.",
    },
    service_apache_inactive: {
      diagnosis: "Apache/Nginx no está activo. Los sitios web devolverán 502/connection refused hasta reiniciarlo.",
      causes: [
        "Crash por configuración inválida tras un reload",
        "OOM killer terminó el proceso por presión de memoria",
        "Reinicio manual del servidor sin auto-arranque",
      ],
      recommendedActions: [
        { key: "restart_apache", label: "Reiniciar Apache/Nginx", primary: true },
        { key: "collect_metrics", label: "Releer métricas" },
        { key: "verify_sites", label: "Verificar sitios" },
      ],
      manualSteps: [
        "Si el reinicio falla, revisa journalctl -u apache2 (o httpd/nginx)",
        "Valida configuración con 'apachectl -t' o 'nginx -t' antes de reintentar",
      ],
      verifyAfter: "Confirmar chip Apache en 'active' y que los sitios degradados vuelvan a OK.",
    },
    service_php_fpm_inactive: {
      diagnosis: "PHP-FPM no está activo. El servidor responde, pero los sitios PHP devolverán 502 Bad Gateway.",
      causes: [
        "Pool agotado por tráfico o código que se queda colgado",
        "OOM killer por consumo de memoria",
        "Configuración de pool inválida tras un reload",
      ],
      recommendedActions: [
        { key: "restart_php_fpm", label: "Reiniciar PHP-FPM", primary: true },
        { key: "collect_metrics", label: "Releer métricas" },
        { key: "verify_sites", label: "Verificar sitios" },
      ],
      manualSteps: [
        "Si vuelve a caer rápido, revisa journalctl -u php*-fpm y aumenta pm.max_children",
        "Identifica el sitio que satura el pool con tail -f /var/log/php*-fpm.log",
      ],
      verifyAfter: "Confirmar chip PHP-FPM en 'active' y que los dominios respondan 200.",
    },
    service_mariadb_inactive: {
      diagnosis: "MariaDB/MySQL no está activo. Cualquier sitio que dependa de la base de datos fallará.",
      causes: [
        "Crash por corrupción de tablas o disco lleno",
        "OOM killer por innodb_buffer_pool sobredimensionado",
        "Reinicio manual sin auto-arranque",
      ],
      recommendedActions: [
        { key: "restart_mariadb", label: "Reiniciar MariaDB/MySQL", primary: true },
        { key: "collect_metrics", label: "Releer métricas" },
      ],
      manualSteps: [
        "Si no arranca, revisa /var/log/mysql/error.log o journalctl -u mariadb",
        "Verifica espacio libre en disco antes de reintentar (df -h)",
      ],
      verifyAfter: "Confirmar chip MariaDB en 'active'. Probar acceso desde un sitio que use BD.",
    },
    resource_cpu_critical: {
      diagnosis: "CPU saturada. La máquina está al máximo o cerca; los sitios responden lento o con timeouts.",
      causes: [
        "Un proceso PHP/cron loopeando o un sitio con tráfico anómalo",
        "Backup, indexación o tarea programada en simultáneo con el horario pico",
        "Bot/scraping concentrado contra un dominio específico",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas y top procesos", primary: true },
        { key: "verify_sites", label: "Verificar sitios" },
        { key: "reload_web_stack", label: "Recargar stack web" },
      ],
      manualSteps: [
        "Mira 'top procesos' del snapshot: si lo monopoliza php-fpm, considera reiniciarlo",
        "Si es un cron/backup en horario pico, reagéndalo fuera de horas",
        "Si es bot/scraping, evalúa fail2ban o un rate-limit en el sitio afectado",
      ],
      verifyAfter: "Tras la acción, vuelve a leer métricas — la CPU debería bajar de los umbrales en 1-2 barridos.",
    },
    resource_ram_critical: {
      diagnosis: "RAM saturada. El sistema empieza a usar swap o el OOM killer puede matar procesos.",
      causes: [
        "PHP-FPM con pm.max_children muy alto para la memoria disponible",
        "innodb_buffer_pool de MariaDB sobredimensionado",
        "Proceso fugado por código del cliente",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas y top procesos", primary: true },
        { key: "restart_php_fpm", label: "Reiniciar PHP-FPM" },
        { key: "verify_sites", label: "Verificar sitios" },
      ],
      manualSteps: [
        "Identifica los top procesos por RAM en el snapshot",
        "Si es PHP-FPM, reinicia para liberar; ajusta max_children si se repite",
        "Considera ampliar memoria del servidor si es recurrente",
      ],
      verifyAfter: "RAM debería bajar tras reiniciar el ofensor. Si no, escalá a ampliar recursos.",
    },
    resource_disk_critical: {
      diagnosis: "Disco crítico. Cuando llega al 100% se rompen logs, sesiones, MariaDB y backups.",
      causes: [
        "Logs sin rotar (apache, nginx, php-fpm)",
        "Backups acumulados sin política de retención",
        "Mailbox/uploads de un dominio creciendo descontrolado",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas", primary: true },
      ],
      manualSteps: [
        "Localiza el directorio que crece: 'du -sh /var/* | sort -h'",
        "Revisa /var/log y /var/backups; rota o purga lo antiguo",
        "Si MariaDB ya falló por disco lleno, libera espacio antes de reiniciarlo",
        "Considera ampliar el volumen si la causa no es transitoria",
      ],
      verifyAfter: "Tras liberar espacio, vuelve a leer métricas para que el incidente se cierre solo.",
    },
    resource_pressure_warning: {
      diagnosis: "El servidor está bajo presión moderada de recursos. Aún no es crítico pero conviene observar.",
      causes: [
        "Pico de tráfico, backup o indexación en curso",
        "Threshold ajustado más bajo que la capacidad real",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas", primary: true },
        { key: "verify_sites", label: "Verificar sitios" },
      ],
      manualSteps: [
        "Observa la tendencia en los próximos 1-2 barridos antes de actuar",
        "Si se sostiene, aplica el playbook del recurso específico",
      ],
      verifyAfter: "Si vuelve bajo umbral en el siguiente barrido, el incidente se resuelve solo.",
    },
    dns_degraded: {
      diagnosis: "El servidor no resuelve DNS. Cualquier salida (paquetes, mail, llamadas externas) puede fallar.",
      causes: [
        "Resolver upstream caído o lento",
        "/etc/resolv.conf mal configurado",
        "Firewall bloqueando UDP/53",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas", primary: true },
      ],
      manualSteps: [
        "Vía SSH: 'dig @1.1.1.1 google.com' para descartar problema upstream",
        "Revisa /etc/resolv.conf y systemd-resolved",
        "Si solo es lento, considera usar resolvers locales (unbound)",
      ],
      verifyAfter: "Tras corregir, vuelve a leer métricas para que el chip DNS marque OK.",
    },
    site_down: {
      diagnosis: "El dominio responde con error o timeout. El sitio está inaccesible para los visitantes.",
      causes: [
        "Apache/Nginx caído o configuración rota tras un deploy",
        "PHP-FPM caído (502 Bad Gateway)",
        "Firewall/WAF bloqueando o sitio en mantenimiento",
      ],
      recommendedActions: [
        { key: "verify_sites", label: "Re-verificar el sitio", primary: true },
        { key: "reload_web_stack", label: "Recargar stack web" },
        { key: "collect_metrics", label: "Releer métricas del servidor" },
      ],
      manualSteps: [
        "Si todos los sitios del server cayeron, aplica el playbook de Apache/PHP-FPM",
        "Si solo este dominio falla, revisa su VirtualHost y .htaccess en busca de cambios recientes",
        "Confirma que no haya regla de firewall específica del cliente",
      ],
      verifyAfter: "Tras 'verify_sites' el chip debería volver a OK; si persiste, escalá a reinicio de servicios.",
    },
    site_slow: {
      diagnosis: "El dominio responde pero por encima del umbral configurado. Experiencia degradada.",
      causes: [
        "CPU/RAM del servidor saturada",
        "Consultas SQL lentas o tabla sin índice",
        "Plugin/tema pesado o caché desactivado",
      ],
      recommendedActions: [
        { key: "collect_metrics", label: "Releer métricas del servidor", primary: true },
        { key: "verify_sites", label: "Re-verificar el sitio" },
        { key: "reload_web_stack", label: "Recargar stack web" },
      ],
      manualSteps: [
        "Si todo el servidor está lento, revisá CPU/RAM/Disco primero",
        "Si solo este sitio: revisar slow log de MariaDB y caché de la app",
        "Considera un APM o New Relic en el cliente si es recurrente",
      ],
      verifyAfter: "El tiempo de respuesta debería volver bajo umbral en los próximos barridos.",
    },
    site_dns_fail: {
      diagnosis: "El dominio no resuelve por DNS desde este monitor. Los visitantes externos probablemente tampoco lo ven.",
      causes: [
        "Cliente cambió DNS y aún no propagó",
        "Registro A apunta a IP equivocada",
        "Proveedor de DNS del cliente caído",
      ],
      recommendedActions: [
        { key: "verify_sites", label: "Re-verificar el sitio", primary: true },
      ],
      manualSteps: [
        "Confirma con 'dig +short DOMINIO' desde otra red",
        "Coordina con el cliente la corrección del registro A/CNAME",
        "Si propagó hace minutos, dale 5-30 min y re-verifica",
      ],
      verifyAfter: "Cuando DNS resuelva, el chip volverá a OK automáticamente.",
    },
    site_ssl_expiring: {
      diagnosis: "El certificado TLS del dominio está por vencer (≤10 días). Si caduca, los navegadores bloquearán el acceso.",
      causes: [
        "Renovación automática (Let's Encrypt/certbot) deshabilitada o fallando",
        "Cambio de DNS que rompió el challenge HTTP-01",
        "Certificado comercial sin renovación contratada",
      ],
      recommendedActions: [
        { key: "verify_sites", label: "Re-verificar el sitio", primary: true },
      ],
      manualSteps: [
        "Vía SSH ejecuta 'certbot renew --dry-run' para diagnosticar",
        "Si el dominio cambió de hosting, valida que el challenge llegue al servidor correcto",
        "Coordina renovación manual si es certificado comercial",
      ],
      verifyAfter: "Tras renovar, el chip SSL debería marcar >30 días en el siguiente barrido.",
    },
    deploy_failure: {
      diagnosis: "El último deploy a este branch terminó con errores. Algunos sitios pueden quedar en versiones mezcladas hasta resolverlo.",
      causes: [
        "Conflicto de git pull / cambios locales no commiteados en el servidor",
        "Permisos / ownership rotos en public_html tras cambios manuales",
        "Acción HTTP post-deploy que falló (migraciones, cache, etc.)",
      ],
      recommendedActions: [],
      manualSteps: [
        "Abre el detalle del deploy en la pestaña Historial y revisa qué sitios fallaron",
        "Para sitios con conflicto git, podés reintentar desde Servidores → Deploy puntual",
        "Si fue una acción HTTP, revisá el log de la acción antes de reintentar",
      ],
      verifyAfter: "Después del retry exitoso, marca el incidente como resuelto manualmente.",
    },
    deploy_rollback: {
      diagnosis: "El deploy falló y se revirtió automáticamente al commit previo. Los sitios deberían estar en la versión anterior estable.",
      causes: [
        "Validación post-deploy detectó sitios degradados y disparó rollback",
        "Falla generalizada de deploy con auto-rollback habilitado",
      ],
      recommendedActions: [
        { key: "verify_sites", label: "Verificar sitios", primary: true },
      ],
      manualSteps: [
        "Confirma en la pestaña Historial que el rollback completó OK",
        "Antes de reintentar el deploy, identifica qué cambio del commit problemático rompía las validaciones",
        "Si el rollback también falló (estado 'rolled_back error'), restaurá manualmente desde el commit anterior",
      ],
      verifyAfter: "Tras verify_sites debería bajar la cantidad de sitios degradados a los valores previos al deploy.",
    },
    deploy_validation_failure: {
      diagnosis: "El deploy terminó pero la validación posterior detectó sitios que empeoraron (más lentos o caídos que antes).",
      causes: [
        "Código del commit introdujo un bug que solo se ve en producción",
        "Plugin/dependencia del cliente incompatible con el cambio",
        "Cache del servidor no se invalidó tras el deploy",
      ],
      recommendedActions: [
        { key: "verify_sites", label: "Re-verificar sitios", primary: true },
        { key: "reload_web_stack", label: "Recargar stack web" },
      ],
      manualSteps: [
        "Identifica qué sitios empeoraron (ver detalle del incidente)",
        "Si el problema persiste, considera rollback manual del deploy",
        "Avisa a desarrollo para revisar el commit que provocó la degradación",
      ],
      verifyAfter: "Cuando los sitios afectados vuelvan a OK, el incidente se cerrará solo.",
    },
  };

  const FALLBACK_PLAYBOOK = {
    diagnosis: "Incidente abierto sin playbook específico. Actúa con las acciones genéricas y revisa snapshot.",
    causes: [],
    recommendedActions: [
      { key: "collect_metrics", label: "Releer métricas", primary: true },
      { key: "verify_sites", label: "Verificar sitios" },
    ],
    manualSteps: [],
    verifyAfter: "Si la causa no está clara, abre el detalle del servidor para ver métricas y top procesos.",
  };

  function derivePlaybookKey(incident) {
    if (!incident) return null;
    const kind = incident.kind || "";
    const title = String(incident.title || "").toLowerCase();
    const details = incident.details || {};

    if (kind === "server_unreachable") return "server_unreachable";
    if (kind === "deploy_validation_failure") return "deploy_validation_failure";
    if (kind === "deploy_rollback") return "deploy_rollback";
    if (kind === "deploy_failure") return "deploy_failure";

    if (kind === "monitor_metric") {
      if (details.service === "apache" || title.includes("apache")) return "service_apache_inactive";
      if (details.service === "php_fpm" || title.includes("php-fpm") || title.includes("php_fpm")) {
        return "service_php_fpm_inactive";
      }
      if (details.service === "mariadb" || title.includes("mariadb") || title.includes("mysql")) {
        return "service_mariadb_inactive";
      }
      if (title.includes("dns")) return "dns_degraded";
      if (title.includes("presión") || title.includes("recursos") || (Array.isArray(details.alerts) && details.alerts.length > 0)) {
        const alerts = (details.alerts || []).join(" ").toLowerCase();
        const isCritical = incident.severity === "critical" || alerts.includes("crítico");
        if (alerts.includes("disco") || details.disk_pct >= 95) return "resource_disk_critical";
        if (alerts.includes("ram") || details.ram_pct >= 90) return "resource_ram_critical";
        if (alerts.includes("cpu") || details.cpu_pct >= 95) return "resource_cpu_critical";
        return isCritical ? "resource_cpu_critical" : "resource_pressure_warning";
      }
    }

    if (kind === "site_health") {
      const status = String(details.status || "").toLowerCase();
      if (details.dns_ok === 0) return "site_dns_fail";
      if (["down", "timeout", "error"].includes(status)) return "site_down";
      if (status === "slow_critical" || status === "slow") return "site_slow";
      if (status === "ssl_expiring" || (Number.isFinite(details.ssl_valid_days) && details.ssl_valid_days <= 10)) {
        return "site_ssl_expiring";
      }
      return "site_down";
    }

    return null;
  }

  function getIncidentPlaybook(incident) {
    const key = derivePlaybookKey(incident);
    return { key, playbook: (key && PLAYBOOKS[key]) || FALLBACK_PLAYBOOK };
  }

  // Calculado una vez al cargar el modulo — el catalogo es estatico.
  const PLAYBOOK_CATALOG = Object.freeze(
    Object.keys(PLAYBOOKS).map((key) =>
      Object.freeze({
        key,
        title: PLAYBOOK_META[key]?.title || key,
        category: PLAYBOOK_META[key]?.category || "server",
        ...PLAYBOOKS[key],
      })
    )
  );

  function getPlaybookCatalog() {
    return PLAYBOOK_CATALOG;
  }

  function getPlaybookCategories() {
    return PLAYBOOK_CATEGORIES;
  }

  function MiniPill({ children, tone = "#64748b" }) {
    return (
      <span
        className="tag"
        style={{
          borderColor: `${tone}55`,
          color: tone,
          background: `${tone}12`,
        }}
      >
        {children}
      </span>
    );
  }

  function IncidentPlaybook({
    incident,
    runningAction = "",
    busy = false,
    onRunIncidentAction,
    onRunRunbook,
    showStatusActions = true,
  }) {
    if (!incident) return null;
    const { playbook } = getIncidentPlaybook(incident);
    const serverId = incident.server_id;
    const incidentId = incident.id;
    const canRunRunbook = Boolean(serverId && typeof onRunRunbook === "function");

    return (
      <div className="incident-playbook">
        <div className="incident-playbook-header">
          <span className="incident-playbook-icon" aria-hidden="true">🩺</span>
          <div className="incident-playbook-text">
            <div className="incident-playbook-title">Diagnóstico</div>
            <div className="incident-playbook-body">{playbook.diagnosis}</div>
          </div>
        </div>

        {playbook.causes && playbook.causes.length > 0 && (
          <div className="incident-playbook-section">
            <div className="incident-playbook-section-title">Causas frecuentes</div>
            <ul className="incident-playbook-list">
              {playbook.causes.map((cause, index) => (
                <li key={`cause-${index}`}>{cause}</li>
              ))}
            </ul>
          </div>
        )}

        {playbook.recommendedActions && playbook.recommendedActions.length > 0 && canRunRunbook && (
          <div className="incident-playbook-section">
            <div className="incident-playbook-section-title">
              <span aria-hidden="true">🛠</span> Acciones recomendadas
            </div>
            <div className="incident-playbook-actions">
              {playbook.recommendedActions.map((action) => {
                const isPrimary = Boolean(action.primary);
                const isRunning = runningAction === runKey.runbook(action.key, serverId);
                return (
                  <button
                    key={action.key}
                    type="button"
                    onClick={() => onRunRunbook(serverId, action.key, incidentId)}
                    disabled={busy}
                    className={`btn btn-xs ${isPrimary ? "btn-primary" : "btn-ghost"}`}
                  >
                    {isRunning ? "Ejecutando..." : action.label}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        {playbook.recommendedActions && playbook.recommendedActions.length > 0 && !canRunRunbook && (
          <div className="incident-playbook-section">
            <div className="incident-playbook-no-server">
              Incidente sin servidor asociado — resolver manualmente siguiendo los pasos abajo.
            </div>
          </div>
        )}

        {playbook.manualSteps && playbook.manualSteps.length > 0 && (
          <div className="incident-playbook-section">
            <div className="incident-playbook-section-title">Pasos manuales</div>
            <ul className="incident-playbook-list">
              {playbook.manualSteps.map((step, index) => (
                <li key={`step-${index}`}>{step}</li>
              ))}
            </ul>
          </div>
        )}

        {playbook.verifyAfter && (
          <div className="incident-playbook-verify">
            <span aria-hidden="true">✅</span>
            <span>{playbook.verifyAfter}</span>
          </div>
        )}

        {showStatusActions && typeof onRunIncidentAction === "function" && (
          <div className="incident-playbook-status-row">
            {incident.status === "open" && (
              <button
                type="button"
                onClick={() => onRunIncidentAction(incidentId, "ack", "Incidente reconocido")}
                disabled={busy}
                className="btn btn-ghost btn-xs"
              >
                {runningAction === runKey.incident("ack", incidentId) ? "Reconociendo..." : "Reconocer"}
              </button>
            )}
            <button
              type="button"
              onClick={() => {
                const ok = typeof window !== "undefined" && window.confirm
                  ? window.confirm(`¿Marcar como resuelto el incidente "${incident.title}"?`)
                  : true;
                if (ok) onRunIncidentAction(incidentId, "resolve", "Incidente resuelto");
              }}
              disabled={busy}
              className="btn btn-xs incident-playbook-resolve-btn"
            >
              {runningAction === runKey.incident("resolve", incidentId) ? "Resolviendo..." : "✓ Resolver"}
            </button>
          </div>
        )}
      </div>
    );
  }

  window.DC = window.DC || {};
  window.DC.getIncidentPlaybook = getIncidentPlaybook;
  window.DC.derivePlaybookKey = derivePlaybookKey;
  window.DC.getPlaybookCatalog = getPlaybookCatalog;
  window.DC.getPlaybookCategories = getPlaybookCategories;

  window.DCComponents = window.DCComponents || {};
  window.DCComponents.IncidentPlaybook = IncidentPlaybook;
  window.DCComponents.IncidentPlaybookPill = MiniPill;
})();
