/* novedades.jsx — el popup «Qué hay de nuevo», el Host que decide cuándo se abre solo y el botón
   «🆕 Novedades» del pie del menú. La lógica (qué, a quién, qué ya vio, el resaltado del CTA) vive en
   lib/novedades.js y los textos en lib/novedades-data.js: acá sólo se dibuja y se decide el momento.
   Expone window.CR.Novedades = { Host, Popup, Boton }. Babel/JSX (escrito con h(), como el resto).

   REGLAS DEL POPUP (las fija harness/verify-novedades.cjs, con una mutación cada una):
   · Se abre SOLO en la pantalla principal del módulo, pasada la carga, sin otro modal abierto, una vez
     por visita a esa pantalla. «🆕 Novedades» lo abre en cualquier pantalla, con TODAS.
   · «Entendido» marca vista la que se ve y pasa a la siguiente sin ver (o cierra). Escape y ✕ cierran
     y marcan vista la que se ve; las demás sin ver vuelven en la próxima visita. Pasar con ← → marca
     vista la que se deja. El CTA la marca, cierra, navega y resalta.
   · Click en el fondo NO cierra: se pierde sin querer y no vuelve.
   · role=dialog, aria-modal, aria-labelledby (título) y aria-describedby (resumen); foco inicial en el
     botón primario, trampa de foco, y al cerrar el foco vuelve a donde estaba. */
(function () {
  var CR = window.CR = window.CR || {};
  var React = window.React, h = React.createElement;
  var useState = React.useState, useEffect = React.useEffect, useRef = React.useRef;

  function api() { return CR.novedades; }
  function fechaCorta(iso) {
    var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(iso || ''));
    return m ? m[3] + '/' + m[2] + '/' + m[1] : String(iso || '');
  }
  var ENFOCABLES = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), '
    + 'textarea:not([disabled]), video[controls], [tabindex]:not([tabindex="-1"])';

  /* Re-dibuja cuando cambia lo visto (desde este popup, desde otro componente o desde otra pestaña). */
  function useCambios() {
    var s = useState(0), set = s[1];
    useEffect(function () {
      return api().suscribir(function (ev) { if (!ev || ev.tipo === 'cambio') set(function (x) { return x + 1; }); });
    }, []);
    return s[0];
  }

  /* Imagen, GIF o video. El video arranca solo, mudo y en loop SALVO con «reducir movimiento»: ahí
     queda quieto en su póster, con controles. Si el archivo no carga, no se muestra un ícono roto. */
  function Media(props) {
    var m = props.media;
    var st = useState(false), fallo = st[0], setFallo = st[1];
    if (!m || !m.src || fallo) return null;
    function alFallar() { setFallo(true); }
    if (m.tipo === 'video') {
      var auto = !api().reducido();
      return h('video', { className: 'nov-media', src: m.src, poster: m.poster || undefined,
        autoPlay: auto, muted: true, loop: true, playsInline: true, controls: !auto,
        preload: auto ? 'auto' : 'metadata', 'aria-label': m.alt || undefined, onError: alFallar,
        // `muted` como atributo además de propiedad: sin él, algunos navegadores no dejan el autoplay
        ref: function (v) { if (v) { v.muted = true; v.setAttribute('muted', ''); } } });
    }
    return h('img', { className: 'nov-media', src: m.src, alt: m.alt || '', loading: 'lazy', decoding: 'async',
      onError: alFallar });
  }

  var contador = 0;
  function Popup(props) {
    var lista = props.lista || [];
    var i = Math.max(0, Math.min(props.indice || 0, lista.length - 1));
    var n = lista[i] || null;
    var ids = useRef(null);
    if (!ids.current) { contador++; ids.current = { titulo: 'nov-titulo-' + contador, resumen: 'nov-resumen-' + contador }; }
    var dialogo = useRef(null), primario = useRef(null);
    var cb = useRef(props); cb.current = props;

    // foco inicial en el botón primario; al cerrar vuelve a donde estaba
    useEffect(function () {
      var antes = document.activeElement;
      if (primario.current) primario.current.focus();
      return function () {
        if (antes && antes !== document.body && antes.isConnected && typeof antes.focus === 'function') {
          try { antes.focus(); } catch (e) { /* nada */ }
        }
      };
    }, []);
    /* Al cambiar de novedad: si el foco quedó afuera o en una flecha que se deshabilitó (fin del
       paginador), va al primario. Sólo en los CAMBIOS: el foco inicial es cosa del efecto de arriba. */
    var indicePrevio = useRef(i);
    useEffect(function () {
      if (indicePrevio.current === i) return;
      indicePrevio.current = i;
      var d = dialogo.current, a = document.activeElement;
      if (d && primario.current && (!d.contains(a) || a.disabled)) primario.current.focus();
    }, [i]);
    /* Teclado en CAPTURA sobre document y frenando Escape: el shell escucha Escape en window para
       cerrar el panel de CRÁNEO y el menú, y no tiene que enterarse de este. */
    useEffect(function () {
      function teclas(e) {
        var d = dialogo.current;
        if (!d) return;
        if (e.key === 'Escape' || e.key === 'Esc') { e.preventDefault(); e.stopPropagation(); cb.current.onCerrar(); return; }
        if (e.key !== 'Tab') return;
        var items = Array.prototype.filter.call(d.querySelectorAll(ENFOCABLES), function (x) { return !x.disabled; });
        if (!items.length) { e.preventDefault(); return; }
        var primero = items[0], ultimo = items[items.length - 1], a = document.activeElement;
        if (!d.contains(a)) { e.preventDefault(); primero.focus(); return; }
        if (e.shiftKey && a === primero) { e.preventDefault(); ultimo.focus(); }
        else if (!e.shiftKey && a === ultimo) { e.preventDefault(); primero.focus(); }
      }
      function alFoco(e) {
        var d = dialogo.current;
        if (d && !d.contains(e.target) && primario.current) primario.current.focus();
      }
      document.addEventListener('keydown', teclas, true);
      document.addEventListener('focusin', alFoco);
      return function () {
        document.removeEventListener('keydown', teclas, true);
        document.removeEventListener('focusin', alFoco);
      };
    }, []);

    /* El fondo no cierra y tampoco se lleva el foco (mousedown sin efecto): se pierde sin querer. */
    function capa(contenido) {
      return h('div', { className: 'nov-capa', 'data-novedades-popup': props.modulo,
        onMouseDown: function (e) { if (e.target === e.currentTarget) e.preventDefault(); } }, contenido);
    }

    if (!n) {
      // «🆕 Novedades» sin nada para este usuario en este módulo
      return capa(h('div', { className: 'nov-dialogo', role: 'dialog', 'aria-modal': 'true',
          'aria-labelledby': ids.current.titulo, 'aria-describedby': ids.current.resumen, ref: dialogo },
        h('div', { className: 'nov-cabecera' },
          h('span', { className: 'nov-emoji', 'aria-hidden': 'true' }, '🆕'),
          h('div', { className: 'nov-cabecera__texto' },
            h('h2', { id: ids.current.titulo, className: 'nov-titulo' }, 'Sin novedades por ahora'))),
        h('p', { id: ids.current.resumen, className: 'nov-resumen' },
          'Cuando actualicemos la plataforma con algo que puedas usar, te lo contamos acá.'),
        h('div', { className: 'nov-pie' },
          h('div', { className: 'nov-acciones' },
            h('button', { type: 'button', className: 'btn btn--primary', ref: primario, 'data-novedad-primario': '',
              onClick: function () { cb.current.onEntendido(); } }, 'Entendido')))));
    }

    var conCta = api().ctaDisponible(n, props.session);
    return capa(h('div', { className: 'nov-dialogo', role: 'dialog', 'aria-modal': 'true',
        'aria-labelledby': ids.current.titulo, 'aria-describedby': ids.current.resumen, ref: dialogo,
        'data-novedad-id': n.id },
      h('div', { className: 'nov-cabecera' },
        h('span', { className: 'nov-emoji', 'aria-hidden': 'true' }, n.emoji || '🆕'),
        h('div', { className: 'nov-cabecera__texto' },
          h('span', { className: 'nov-chip' }, 'v' + n.version + (n.fecha ? ' · ' + fechaCorta(n.fecha) : '')),
          h('h2', { id: ids.current.titulo, className: 'nov-titulo' }, n.titulo)),
        h('button', { type: 'button', className: 'nov-cerrar', 'aria-label': 'Cerrar novedades', title: 'Cerrar (Esc)',
          onClick: function () { cb.current.onCerrar(); } }, '✕')),
      h('p', { id: ids.current.resumen, className: 'nov-resumen' }, n.resumen),
      n.puntos && n.puntos.length ? h('ul', { className: 'nov-puntos' }, n.puntos.map(function (p, k) {
        return h('li', { key: k },
          h('span', { className: 'nov-puntos__emoji', 'aria-hidden': 'true' }, p.emoji || '•'),
          h('span', null, p.texto));
      })) : null,
      h(Media, { key: n.id, media: n.media }),
      h('div', { className: 'nov-pie' },
        lista.length > 1 ? h('div', { className: 'nov-paginador', role: 'group', 'aria-label': 'Cambiar de novedad' },
          h('button', { type: 'button', className: 'nov-pag', 'aria-label': 'Novedad anterior', disabled: i === 0,
            onClick: function () { cb.current.onIr(i - 1); } }, '←'),
          h('span', { className: 'nov-pag__txt', 'aria-live': 'polite' }, (i + 1) + ' de ' + lista.length),
          h('button', { type: 'button', className: 'nov-pag', 'aria-label': 'Novedad siguiente', disabled: i >= lista.length - 1,
            onClick: function () { cb.current.onIr(i + 1); } }, '→')) : null,
        h('div', { className: 'nov-acciones' },
          conCta ? h('button', { type: 'button', className: 'btn btn--ghost',
            onClick: function () { cb.current.onEntendido(); } }, 'Entendido') : null,
          h('button', { type: 'button', className: 'btn btn--primary', ref: primario, 'data-novedad-primario': '',
            onClick: function () { if (conCta) cb.current.onCta(); else cb.current.onEntendido(); } },
            conCta ? (n.cta.texto || 'Mostrame dónde') : 'Entendido')))));
  }

  /* Vive en el shell de cada módulo (app.jsx para Cráneo Rutas, screens/last-mile.jsx para Last
     Mile), en TODAS sus pantallas: así «🆕 Novedades» lo abre desde cualquiera. Solo, se abre sólo
     con `principal` y sin `cargando`.
     props: { modulo: 'rutas'|'lastMile', session, ruta, principal, cargando } */
  function Host(props) {
    var st = useState(null), abierto = st[0], setAbierto = st[1];   // { modo: 'solo'|'todas', lista, indice }
    var cb = useRef(props); cb.current = props;
    var estadoRef = useRef(null); estadoRef.current = abierto;
    /* Ya se abrió (o se cerró) en ESTA visita a la principal: no se vuelve a abrir solo hasta salir y
       volver. Sin esto, Escape cerraba y a los 800 ms aparecía la siguiente sin ver. */
    var yaEnEstaVisita = useRef(false);
    var usuario = api().claveMemoria(props.session, props.modulo);


    useEffect(function () {
      return api().suscribir(function (ev) {
        if (!ev || ev.tipo !== 'abrir' || ev.modulo !== cb.current.modulo) return;
        yaEnEstaVisita.current = true;
        setAbierto({ modo: 'todas', lista: api().todas(cb.current.session, cb.current.modulo), indice: 0 });
      });
    }, []);

    // salir de la principal habilita la próxima visita (y se lleva el popup que se abrió solo)
    useEffect(function () {
      if (props.principal) return;
      yaEnEstaVisita.current = false;
      if (estadoRef.current && estadoRef.current.modo === 'solo') setAbierto(null);
    }, [props.principal]);
    useEffect(function () { yaEnEstaVisita.current = false; }, [usuario]);

    // cambiar de pantalla cierra el popup, sin marcar nada
    var rutaPrevia = useRef(props.ruta);
    useEffect(function () {
      if (rutaPrevia.current === props.ruta) return;
      rutaPrevia.current = props.ruta;
      setAbierto(null);
    }, [props.ruta]);

    // apertura sola: principal, sin carga, sin otro modal, una vez por visita
    useEffect(function () {
      if (abierto || !props.principal || props.cargando || !props.session || yaEnEstaVisita.current) return;
      var vivo = true, t = null;
      function probar() {
        if (!vivo) return;
        var p = cb.current, a = api();
        if (!a.puedeAbrirSolo(p.session, p.modulo)) return;
        if (a.bloqueado()) { t = setTimeout(probar, a.config.reintentoMs); return; }
        var lista = a.pendientes(p.session, p.modulo);
        if (!lista.length) return;
        a.registrarAperturaSola(p.session, p.modulo);
        yaEnEstaVisita.current = true;
        setAbierto({ modo: 'solo', lista: lista, indice: 0 });
      }
      t = setTimeout(probar, api().config.demoraMs);
      return function () { vivo = false; clearTimeout(t); };
    }, [!!abierto, props.principal, props.cargando, usuario, props.modulo]);

    function actual() { return abierto && abierto.lista[abierto.indice]; }
    function marcar(n) { if (n) api().marcarVista(cb.current.session, cb.current.modulo, n.id); }
    function ir(j) {
      if (!abierto || j < 0 || j >= abierto.lista.length || j === abierto.indice) return;
      marcar(actual());   // la deja: la vio
      setAbierto(Object.assign({}, abierto, { indice: j }));
    }
    function entendido() {
      marcar(actual());
      if (abierto) {
        var vistas = api().leerVistas(cb.current.session, cb.current.modulo);
        for (var j = abierto.indice + 1; j < abierto.lista.length; j++) {
          if (!vistas[abierto.lista[j].id]) { setAbierto(Object.assign({}, abierto, { indice: j })); return; }
        }
      }
      setAbierto(null);
    }
    function cerrar() { marcar(actual()); setAbierto(null); }
    function irAlCta() {
      var n = actual();
      marcar(n);
      setAbierto(null);
      var cta = n && n.cta;
      if (!cta || !cta.ruta) return;
      if (window.location.hash !== cta.ruta) window.location.hash = cta.ruta;
      if (cta.resaltar) api().resaltar({ selector: cta.resaltar, texto: cta.textoResaltado || n.titulo });
    }

    if (!abierto) return null;
    return h(Popup, { modulo: props.modulo, session: props.session, lista: abierto.lista, indice: abierto.indice,
      onIr: ir, onEntendido: entendido, onCerrar: cerrar, onCta: irAlCta });
  }

  /* «🆕 Novedades» al pie del menú, junto a «Modo claro». El puntito dice que hay alguna sin ver.
     `onAntes` cierra el menú móvil antes de abrir (dos modales a la vez no). */
  function Boton(props) {
    useCambios();
    if (!props.modulo) return null;
    var n = api().pendientes(props.session, props.modulo).length;
    return h('button', { type: 'button', className: 'sidebar-action sidebar-action--novedades',
        'data-novedades-boton': props.modulo, 'aria-label': n ? 'Novedades (' + n + ' sin ver)' : 'Novedades',
        onClick: function () { if (props.onAntes) props.onAntes(); api().abrirTodas(props.modulo); } },
      h('span', { className: 'nov-ico', 'aria-hidden': 'true' }, '🆕'),
      h('span', null, 'Novedades'),
      n ? h('span', { className: 'nov-punto', 'aria-hidden': 'true', title: n + ' sin ver' }) : null);
  }

  /* Un error en el popup o en el botón NO puede tirar la pantalla de operación: el Host vive fuera
     del Error Boundary de las pantallas (en el shell) y un throw ahí dejaría el módulo en blanco. Si
     algo de esto falla, se registra en la consola y la novedad simplemente no aparece. */
  var Aislado = (function () {
    return class extends React.Component {
      constructor(p) { super(p); this.state = { err: null }; }
      static getDerivedStateFromError(err) { return { err: err }; }
      componentDidCatch(err) { if (window.console) window.console.error('[novedades]', err); }
      render() { return this.state.err ? null : this.props.children; }
    };
  })();
  function aislar(Componente) { return function (props) { return h(Aislado, null, h(Componente, props)); }; }

  CR.Novedades = { Host: aislar(Host), Popup: Popup, Boton: aislar(Boton) };
})();
