/* global React */
const { useState, useEffect, useRef, useCallback } = React;

/* ============================================================
   FixRiver — Automatizaciones (showcase)
   Catálogo de automatizaciones Make + n8n.
   Make: capturas AVIF servidas desde el CDN (card en la grilla,
   full en el lightbox). n8n: visor interactivo del workflow real.
   ============================================================ */

const MAKE = '167,139,250';   // iris / violet  → Make
const N8N  = '240,96,143';    // magenta        → n8n

const IMG_BASE = 'https://cdn.fixriver.com/assets/automations/';
const mkImg = (slug) => ({ img: IMG_BASE + slug + '-card.avif', imgFull: IMG_BASE + slug + '-full.avif' });

// Orden mezclado (Make y n8n alternados) para que la grilla no quede en dos bloques.
// Make: captura del CDN. n8n: visor interactivo del workflow real (N8nViewer).
// Los slugs de Make saltan de make-08 a make-10: es intencional, make-09 no existe.
const AUTOS = [
  { id:'make-01-alfred', p:'make', title:'Alfred — Asistente multipropósito por WhatsApp',
    desc:'Un solo número de WhatsApp que lo hace todo: emite facturas, resume tus emails, transcribe audios y hasta responde con voz. Detecta la intención con IA, filtra intentos de manipulación y registra cada operación. La demo de lo que puede ser un asistente de empresa.',
    ...mkImg('make-01-alfred') },
  { id:'n8n-01', p:'n8n', title:'Asistente de RRHH por WhatsApp (multi-agente)',
    desc:'Varios agentes IA coordinados resuelven las dudas de empleados por WhatsApp y derivan al equipo solo cuando hace falta.',
    img:null, workflow:'workflows/01-asistente-rrhh-whatsapp.json' },
  { id:'make-02-chat-master-ia', p:'make', title:'Chat Master IA — Captación del Máster en IA (BIG + Universidad Isabel I)',
    desc:'En producción: recibía por WhatsApp a los interesados en el máster, identificaba al contacto, transcribía los audios entrantes y sostenía la conversación con un asistente IA con memoria e historial. La puerta de entrada del embudo de captación.',
    ...mkImg('make-02-chat-master-ia') },
  { id:'n8n-02', p:'n8n', title:'Copiloto de operaciones por Telegram',
    desc:'Un copiloto IA en Telegram consulta tus sistemas y ejecuta tareas operativas desde el chat del equipo.',
    img:null, workflow:'workflows/02-copiloto-operaciones-telegram.json' },
  { id:'make-03-chat-respuesta', p:'make', title:'Chat Respuesta — Motor de respuesta del asistente',
    desc:'La segunda mitad del sistema, con un detalle que casi ningún bot resuelve bien: si el interesado manda tres mensajes seguidos, no recibe tres respuestas sueltas — el flujo los agrupa y contesta con una sola que responde a todo junto, como lo haría una persona. Esa arquitectura modular terminó siendo material de clase para los propios alumnos del máster.',
    ...mkImg('make-03-chat-respuesta') },
  { id:'n8n-03', p:'n8n', title:'Funnel de registro conversacional por WhatsApp',
    desc:'Guía al usuario paso a paso por WhatsApp hasta completar el registro, sin formularios ni fricción.',
    img:null, workflow:'workflows/03-funnel-registro-whatsapp.json' },
  { id:'make-04-emitir-factura', p:'make', title:'Emitir Factura — Facturación end-to-end',
    desc:'El sistema que factura en producción: identifica al cliente, resuelve método de pago, conversión de divisas y numeración secuencial, genera el PDF con formato desde plantilla y lo archiva con su registro contable. Facturar sin tocar nada.',
    ...mkImg('make-04-emitir-factura') },
  { id:'n8n-04', p:'n8n', title:'Certificados automáticos de cursos',
    desc:'Genera y envía el certificado personalizado en cuanto el alumno termina el curso.',
    img:null, workflow:'workflows/04-certificados-automaticos.json' },
  { id:'make-05-gcalendar', p:'make', title:'Agenda en Google Calendar por WhatsApp',
    desc:'Tus clientes consultan disponibilidad, reservan y cancelan turnos directamente por WhatsApp; todo queda sincronizado en Google Calendar al instante. Recepción abierta 24/7 sin recepcionista.',
    ...mkImg('make-05-gcalendar') },
  { id:'n8n-05', p:'n8n', title:'Subtitulado automático de academias de video',
    desc:'Transcribe y subtitula cada video de la academia automáticamente, listo para publicar.',
    img:null, workflow:'workflows/05-subtitulado-automatico.json' },
  { id:'make-06-envio-whatsapp', p:'make', title:'Envío WhatsApp — Recordatorios de turnos a escala',
    desc:'Más de 150 módulos en producción: lee los turnos registrados en Calendar y dispara recordatorios por WhatsApp y email en cada hito, con rutas para confirmaciones y avisos. Menos ausencias, agenda llena.',
    ...mkImg('make-06-envio-whatsapp') },
  { id:'n8n-06', p:'n8n', title:'Resúmenes de clases generados por IA',
    desc:'Convierte la grabación de cada clase en un resumen con los puntos clave para repasar en minutos.',
    img:null, workflow:'workflows/06-resumenes-ia.json' },
  { id:'make-07-sheets-holded', p:'make', title:'Sheets → Holded — Contabilidad de alquiler turístico',
    desc:'En producción: toma la exportación de un sistema de gestión legacy, canaliza las reservas de una gestora de propiedades turísticas y las contabiliza y factura automáticamente en Holded. Del Excel viejo a la contabilidad ordenada.',
    ...mkImg('make-07-sheets-holded') },
  { id:'n8n-07', p:'n8n', title:'Conciliación de pagos con Stripe',
    desc:'Cruza los cobros de Stripe con tu contabilidad y señala cada diferencia para revisarla.',
    img:null, workflow:'workflows/07-conciliacion-stripe.json' },
  { id:'make-08-recepcion-myc', p:'make', title:'Recepción MYC — Prototipo operativo de 300+ módulos',
    desc:'Una prueba de concepto funcional a gran escala: 300+ módulos orquestando toda la operación de recepción por WhatsApp, para validar el modelo antes de pasarlo a desarrollo en código. Parte del proyecto Moyca (ver Proyectos).',
    ...mkImg('make-08-recepcion-myc') },
  { id:'n8n-08', p:'n8n', title:'Motor de campañas masivas por WhatsApp API',
    desc:'Envía campañas segmentadas por la API oficial de WhatsApp con control de ritmo y entregas.',
    img:null, workflow:'workflows/08-campanas-whatsapp.json' },
  { id:'make-10-transcripciones', p:'make', title:'Process Transcriptions — 3 toneladas de cintas de radio',
    desc:'Uno de los primeros proyectos: años de emisiones de radio digitalizadas desde más de 3 toneladas de cintas magnéticas, transcritas en serie y luego resumidas y etiquetadas con IA. Memoria histórica convertida en archivo consultable.',
    ...mkImg('make-10-transcripciones') },
  { id:'n8n-09', p:'n8n', title:'Sistema self-healing de mensajería',
    desc:'Detecta los errores de envío y reintenta o reencamina los mensajes solo, sin intervención.',
    img:null, workflow:'workflows/09-self-healing.json' },
  { id:'n8n-10', p:'n8n', title:'Unificación de usuarios duplicados',
    desc:'Detecta y fusiona registros duplicados para mantener una única ficha por usuario.',
    img:null, workflow:'workflows/10-unificacion-duplicados.json' },
];

const PLATFORMS = {
  make: { name:'Make',  c: MAKE, icon:'puzzle' },
  n8n:  { name:'n8n',   c: N8N,  icon:'workflow' },
};

/* ---------- minimapa SVG del workflow real (tarjetas n8n) ----------
   Dibuja nodos y conexiones con las posiciones reales del JSON
   (precalculadas en thumbs.js para no descargar los workflows en la grilla). */
const NODE = 96; // tamaño de nodo en coordenadas del lienzo de n8n

function WorkflowThumb({ id, title }) {
  const t = (window.WF_THUMBS || {})[id];
  if (!t) return null;
  const xs = t.n.map(p => p[0]), ys = t.n.map(p => p[1]);
  const pad = 70;
  const minX = Math.min(...xs) - pad, minY = Math.min(...ys) - pad;
  const w = Math.max(...xs) - minX + NODE + pad, h = Math.max(...ys) - minY + NODE + pad;
  return (
    <svg className="wf__map" viewBox={`${minX} ${minY} ${w} ${h}`}
         preserveAspectRatio="xMidYMid meet" role="img"
         aria-label={'Diagrama del flujo de trabajo en n8n: ' + title}>
      {t.e.map(([from, to], i) => {
        const sx = t.n[from][0] + NODE, sy = t.n[from][1] + NODE / 2;
        const ex = t.n[to][0],          ey = t.n[to][1] + NODE / 2;
        const dx = Math.max(50, Math.abs(ex - sx) / 2);
        return <path key={i} d={`M ${sx} ${sy} C ${sx + dx} ${sy}, ${ex - dx} ${ey}, ${ex} ${ey}`} />;
      })}
      {t.n.map(([x, y, trig], i) => trig
        ? <circle key={i} cx={x + NODE / 2} cy={y + NODE / 2} r={NODE / 2} />
        : <rect key={i} x={x} y={y} width={NODE} height={NODE} rx={NODE * 0.26} />)}
    </svg>
  );
}

/* ---------- placeholder que evoca un lienzo de automatización ---------- */
function Shot({ a, color }) {
  const pf = PLATFORMS[a.p];
  const c = color ? pf.c : '148,163,184';
  // Si la captura del CDN no carga, se cae al placeholder de lienzo en vez de
  // dejar el icono de imagen rota.
  const [rota, setRota] = useState(false);
  if (a.img && !rota) {
    return (
      <div className="shot">
        <img src={a.img} alt={'Automatización en Make: ' + a.title} loading="lazy" decoding="async" onError={() => setRota(true)} />
      </div>
    );
  }
  const thumb = a.workflow && (window.WF_THUMBS || {})[a.id];
  if (thumb) {
    return (
      <div className="shot wf" style={{ '--c': c }}>
        <div className="ph__grid" />
        <WorkflowThumb id={a.id} title={a.title} />
        <span className="ph__name">{thumb.n.length} nodos · míralo por dentro</span>
      </div>
    );
  }
  return (
    <div className="shot ph" style={{ '--c': c }}>
      <div className="ph__grid" />
      <div className="ph__flow">
        <span className="ph__node ph__node--trig" />
        <span className="ph__wire" />
        <span className="ph__node" />
        <span className="ph__wire" />
        <span className="ph__node" />
        <span className="ph__wire" />
        <span className="ph__node ph__node--end" />
      </div>
      <span className="ph__name">{a.id}.avif</span>
    </div>
  );
}

function Badge({ p, color }) {
  const pf = PLATFORMS[p];
  return (
    <span className="badge" style={{ '--c': color ? pf.c : '148,163,184' }}>
      <i data-lucide={pf.icon} />{pf.name}
    </span>
  );
}

/* div[role=button] y no <button>: la tarjeta lleva flow content (div/h2/p),
   inválido dentro de un botón; el aria-label da un nombre corto en vez de
   todo el texto de la tarjeta. Enter/Espacio replican el comportamiento nativo. */
function abrirConTeclado(onOpen) {
  return (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onOpen(); } };
}

function Card({ a, color, onOpen, span }) {
  const pf = PLATFORMS[a.p];
  return (
    <div className={'card reveal' + (span ? ' card--wide' : '')}
         style={{ '--c': color ? pf.c : '148,163,184' }}
         role="button" tabIndex={0} aria-label={'Ver ' + a.title}
         onClick={onOpen} onKeyDown={abrirConTeclado(onOpen)}>
      <Shot a={a} color={color} />
      <span className="card__zoom"><i data-lucide="maximize-2" /></span>
      <div className="card__body">
        <Badge p={a.p} color={color} />
        <h2>{a.title}</h2>
        <p>{a.desc}</p>
      </div>
    </div>
  );
}

function Row({ a, color, onOpen }) {
  const pf = PLATFORMS[a.p];
  return (
    <div className="row reveal" style={{ '--c': color ? pf.c : '148,163,184' }}
         role="button" tabIndex={0} aria-label={'Ver ' + a.title}
         onClick={onOpen} onKeyDown={abrirConTeclado(onOpen)}>
      <div className="row__shot"><Shot a={a} color={color} /><span className="card__zoom"><i data-lucide="maximize-2" /></span></div>
      <div className="row__body">
        <Badge p={a.p} color={color} />
        <h2>{a.title}</h2>
        <p>{a.desc}</p>
      </div>
    </div>
  );
}

/* ---------- Lightbox ---------- */

/* Captura a tamaño completo con fallback: si el CDN falla, cae al placeholder
   de lienzo (key por id en el call-site para resetear el estado al navegar). */
function FullShot({ a, color }) {
  const [rota, setRota] = useState(false);
  if (rota) return <Shot a={{ ...a, img: null }} color={color} />;
  return <img src={a.imgFull || a.img} alt={'Automatización en Make: ' + a.title} onError={() => setRota(true)} />;
}

function Lightbox({ list, idx, color, onClose, onStep }) {
  const abierto = idx != null;
  const rootRef = useRef(null);
  const focoPrevio = useRef(null);
  useEffect(() => {
    if (!abierto) return;
    function key(e){
      if (e.key === 'Escape') onClose();
      else if (e.key === 'ArrowLeft') onStep(-1);
      else if (e.key === 'ArrowRight') onStep(1);
      else if (e.key === 'Tab') {
        // focus trap: aria-modal solo lo declara, no lo impone — sin esto
        // Tab se escapa a la página de fondo
        const root = rootRef.current;
        if (!root) return;
        const focables = root.querySelectorAll('button, a[href]');
        if (!focables.length) return;
        const primero = focables[0], ultimo = focables[focables.length - 1];
        if (e.shiftKey && document.activeElement === primero) { e.preventDefault(); ultimo.focus(); }
        else if (!e.shiftKey && (document.activeElement === ultimo || !root.contains(document.activeElement))) { e.preventDefault(); primero.focus(); }
      }
    }
    addEventListener('keydown', key);
    return () => removeEventListener('keydown', key);
  }, [abierto, onClose, onStep]);
  // gestión de foco del diálogo: al abrir, foco al botón de cerrar;
  // al cerrar, de vuelta a la tarjeta que lo abrió
  useEffect(() => {
    if (!abierto) return;
    focoPrevio.current = document.activeElement;
    const x = rootRef.current && rootRef.current.querySelector('.lb__x');
    if (x) x.focus();
    return () => {
      const prev = focoPrevio.current;
      if (prev && typeof prev.focus === 'function' && document.contains(prev)) prev.focus();
    };
  }, [abierto]);
  // Con el modal abierto, la página de fondo no debe scrollear.
  useEffect(() => {
    if (!abierto) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, [abierto]);
  if (idx == null) return null;
  // El filtro puede encoger la lista con el modal abierto (p. ej. tabulando
  // hasta los filtros de detrás): sin esta guarda, list[idx] = undefined
  // tiraría abajo todo el árbol de React.
  const a = list[idx];
  if (!a) return null;
  const pf = PLATFORMS[a.p];
  const c = color ? pf.c : '148,163,184';
  return (
    <div className="lb on" role="dialog" aria-modal="true" aria-label={a.title} ref={rootRef}
         onClick={(e)=> e.target.classList.contains('lb') && onClose()}>
      <button className="lb__x" onClick={onClose} aria-label="Cerrar"><i data-lucide="x" /></button>
      <button className="lb__nav lb__nav--prev" onClick={(e)=>{e.stopPropagation();onStep(-1);}} aria-label="Anterior"><i data-lucide="chevron-left" /></button>
      <div className="lb__stage" style={{ '--c': c }}>
        <div className="lb__media">
          {a.workflow
            ? <N8nViewer src={a.workflow} />
            : a.img
              ? <FullShot key={a.id} a={a} color={color} />
              : <Shot a={a} color={color} />}
        </div>
        <div className="lb__info">
          {/* key por plataforma: frIcons reemplaza el <i> por <svg> y React ya no
              puede actualizar el icono al alternar Make↔n8n; remontar el badge
              fuerza un <i data-lucide> fresco que recoge el frIcons del efecto */}
          <Badge key={a.p} p={a.p} color={color} />
          <h2>{a.title}</h2>
          <p>{a.desc}</p>
          <div className="lb__cta">
            <span>¿Es esta la automatización para tu negocio?</span>
            <a href={'https://wa.me/34623982404?text=' + encodeURIComponent('¡Hola! Vengo de la web. Me interesa la automatización «' + a.title + '» para mi negocio.')}
               target="_blank" rel="noopener">
              <i data-lucide="message-circle" />Contacta con nosotros
            </a>
          </div>
          <span className="lb__count">{idx + 1} / {list.length}</span>
        </div>
      </div>
      <button className="lb__nav lb__nav--next" onClick={(e)=>{e.stopPropagation();onStep(1);}} aria-label="Siguiente"><i data-lucide="chevron-right" /></button>
    </div>
  );
}

function Section({ t }) {
  const [filter, setFilter] = useState('all');
  const [lb, setLb] = useState(null);
  const color = t.colorCode !== false;
  const layout = t.layout || 'grid';

  const list = AUTOS.filter(a => filter === 'all' || a.p === filter);
  const counts = {
    all: AUTOS.length,
    make: AUTOS.filter(a=>a.p==='make').length,
    n8n: AUTOS.filter(a=>a.p==='n8n').length,
  };

  // Redibuja los iconos del sprite tras cada render. window.frIcons lo define site.js; si por
  // orden de carga aún no existe (site.js llegó después que React), el primer render quedaría sin
  // iconos y el efecto sin deps no reintenta solo → poll acotado de una sola vez hasta que aparezca
  // (o se rinde a los ~2 s). Sin dependencias nuevas.
  useEffect(() => {
    if (window.frIcons) { window.frIcons(); return; }
    let n = 0;
    const id = setInterval(() => {
      if (window.frIcons) { window.frIcons(); clearInterval(id); }
      else if (++n > 40) clearInterval(id);
    }, 50);
    return () => clearInterval(id);
  });

  // Scroll-reveal de las tarjetas: entran al asomar al viewport (clase .reveal + IntersectionObserver,
  // como el resto del sitio), y las que aparecen a la vez lo hacen escalonadas de arriba a abajo. Se
  // re-observa tras cada render (incluye cambios de filtro, que crean tarjetas nuevas). Sin deps nuevas.
  useEffect(() => {
    const targets = document.querySelectorAll('#root .card.reveal:not(.in), #root .row.reveal:not(.in)');
    if (!targets.length) return;
    const revealAll = () => targets.forEach(el => el.classList.add('in'));
    if (!('IntersectionObserver' in window)) { revealAll(); return; }
    let ioFired = false;
    const io = new IntersectionObserver((entries, obs) => {
      ioFired = true;
      entries.filter(e => e.isIntersecting)
        .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
        .forEach((e, k) => {
          obs.unobserve(e.target);
          const el = e.target;
          setTimeout(() => el.classList.add('in'), Math.min(k, 8) * 55);
        });
    }, { rootMargin: '0px 0px -8% 0px' });
    targets.forEach(el => io.observe(el));
    // Red de seguridad: si el IO nunca dispara (p. ej. viewport sin altura o navegador raro), revela
    // todo a los 2.5 s para que las tarjetas NUNCA queden invisibles. Si el IO funciona no toca nada
    // (ioFired=true) → el scroll-reveal queda intacto.
    const safety = setTimeout(() => { if (!ioFired) revealAll(); }, 2500);
    return () => { io.disconnect(); clearTimeout(safety); };
  });

  const step = useCallback((d) => {
    setLb(i => (i == null ? i : (i + d + list.length) % list.length));
  }, [list.length]);

  // bento spans: every 5th card (within filtered list) goes wide for rhythm
  const isWide = (i) => layout === 'bento' && i % 5 === 0;

  const FILTERS = [
    ['all','Todas','layers'],
    ['make','Make','puzzle'],
    ['n8n','n8n','workflow'],
  ];

  return (
    <section className={'auto auto--' + layout + (t.density === 'compact' ? ' is-compact':'')} id="automatizaciones" data-screen-label="Automatizaciones (showcase)">
      <div className="auto__inner">
        <div className="auto__head reveal in">
          <div className="eyebrow">Automatizaciones</div>
          <h1>El trabajo repetitivo, <span className="grad">resuelto.</span></h1>
          <p>Automatizaciones funcionando en negocios como el tuyo. Cada flujo conecta las herramientas que ya usas y elimina una tarea de tu semana. Filtra por plataforma y míralos por dentro.</p>
        </div>

        {/* botones-filtro con aria-pressed, no tabs: role="tab" sin aria-selected
            ni tabpanel asociado prometía al lector de pantalla un patrón que no existe */}
        <div className="filters reveal in">
          {FILTERS.map(([k,label,icon]) => (
            <button key={k} aria-pressed={filter===k}
                    className={'filt' + (filter===k ? ' on':'')}
                    style={{ '--c': k==='make'?MAKE : k==='n8n'?N8N : '45,226,192' }}
                    onClick={()=>{ setFilter(k); setLb(null); }}>
              <i data-lucide={icon} />{label}<b>{counts[k]}</b>
            </button>
          ))}
        </div>

        {layout === 'lista' ? (
          <div className="rows reveal in">
            {list.map((a, i) => <Row key={a.id} a={a} color={color} onOpen={()=>setLb(i)} />)}
          </div>
        ) : (
          <div className={'grid reveal in' + (layout==='bento'?' grid--bento':'')}>
            {list.map((a, i) => <Card key={a.id} a={a} color={color} span={isWide(i)} onOpen={()=>setLb(i)} />)}
          </div>
        )}
      </div>

      <Lightbox list={list} idx={lb} color={color} onClose={()=>setLb(null)} onStep={step} />
    </section>
  );
}

Object.assign(window, { Section });
