/* 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 — Multipurpose WhatsApp assistant',
    desc:'A single WhatsApp number that does it all: issues invoices, summarizes your emails, transcribes audio, and even replies with voice. It detects intent with AI, filters out manipulation attempts, and logs every operation. The demo of what a company assistant can be.',
    ...mkImg('make-01-alfred') },
  { id:'n8n-01', p:'n8n', title:'HR assistant on WhatsApp (multi-agent)',
    desc:'Several coordinated AI agents resolve employee questions on WhatsApp and hand off to the team only when needed.',
    img:null, workflow:'workflows/01-asistente-rrhh-whatsapp.json' },
  { id:'make-02-chat-master-ia', p:'make', title:'Chat Master AI — Lead capture for the Master\'s in AI (BIG + Universidad Isabel I)',
    desc:'In production: it greeted people interested in the Master\'s on WhatsApp, identified the contact, transcribed incoming audio, and carried the conversation with an AI assistant that kept memory and history. The entry point of the lead-capture funnel.',
    ...mkImg('make-02-chat-master-ia') },
  { id:'n8n-02', p:'n8n', title:'Operations copilot on Telegram',
    desc:'An AI copilot in Telegram queries your systems and runs operational tasks straight from the team chat.',
    img:null, workflow:'workflows/02-copiloto-operaciones-telegram.json' },
  { id:'make-03-chat-respuesta', p:'make', title:'Chat Reply — the assistant\'s response engine',
    desc:'The second half of the system, with a detail almost no bot gets right: if a lead sends three messages in a row, they don\'t get three separate replies — the flow groups them and answers with a single message that addresses everything at once, the way a person would. That modular architecture ended up becoming class material for the Master\'s students themselves.',
    ...mkImg('make-03-chat-respuesta') },
  { id:'n8n-03', p:'n8n', title:'Conversational sign-up funnel on WhatsApp',
    desc:'Guides the user step by step on WhatsApp until sign-up is complete, with no forms and no friction.',
    img:null, workflow:'workflows/03-funnel-registro-whatsapp.json' },
  { id:'make-04-emitir-factura', p:'make', title:'Issue Invoice — end-to-end invoicing',
    desc:'The system that invoices in production: it identifies the customer, handles payment method, currency conversion, and sequential numbering, generates the formatted PDF from a template, and files it with its accounting record. Invoicing without lifting a finger.',
    ...mkImg('make-04-emitir-factura') },
  { id:'n8n-04', p:'n8n', title:'Automatic course certificates',
    desc:'Generates and sends the personalized certificate the moment the student finishes the course.',
    img:null, workflow:'workflows/04-certificados-automaticos.json' },
  { id:'make-05-gcalendar', p:'make', title:'Google Calendar scheduling on WhatsApp',
    desc:'Your customers check availability, book, and cancel appointments right on WhatsApp; everything syncs to Google Calendar instantly. A front desk open 24/7 with no receptionist.',
    ...mkImg('make-05-gcalendar') },
  { id:'n8n-05', p:'n8n', title:'Automatic subtitling for video academies',
    desc:'Transcribes and subtitles every academy video automatically, ready to publish.',
    img:null, workflow:'workflows/05-subtitulado-automatico.json' },
  { id:'make-06-envio-whatsapp', p:'make', title:'WhatsApp Send — appointment reminders at scale',
    desc:'More than 150 modules in production: it reads the appointments logged in Calendar and fires reminders over WhatsApp and email at every milestone, with paths for confirmations and alerts. Fewer no-shows, a full schedule.',
    ...mkImg('make-06-envio-whatsapp') },
  { id:'n8n-06', p:'n8n', title:'AI-generated class summaries',
    desc:'Turns each class recording into a summary with the key points to review in minutes.',
    img:null, workflow:'workflows/06-resumenes-ia.json' },
  { id:'make-07-sheets-holded', p:'make', title:'Sheets → Holded — vacation-rental accounting',
    desc:'In production: it takes the export from a legacy management system, channels the bookings of a vacation-rental property manager, and automatically records and invoices them in Holded. From the old Excel to clean books.',
    ...mkImg('make-07-sheets-holded') },
  { id:'n8n-07', p:'n8n', title:'Stripe payment reconciliation',
    desc:'Cross-checks Stripe charges against your accounting and flags every discrepancy for review.',
    img:null, workflow:'workflows/07-conciliacion-stripe.json' },
  { id:'make-08-recepcion-myc', p:'make', title:'MYC Reception — operational prototype with 300+ modules',
    desc:'A working proof of concept at large scale: 300+ modules orchestrating the entire front-desk operation over WhatsApp, to validate the model before moving it to code development. Part of the Moyca project (see Projects).',
    ...mkImg('make-08-recepcion-myc') },
  { id:'n8n-08', p:'n8n', title:'Bulk campaign engine on the WhatsApp API',
    desc:'Sends segmented campaigns through the official WhatsApp API with rate control and delivery tracking.',
    img:null, workflow:'workflows/08-campanas-whatsapp.json' },
  { id:'make-10-transcripciones', p:'make', title:'Process Transcriptions — 3 tons of radio tapes',
    desc:'One of the earliest projects: years of radio broadcasts digitized from more than 3 tons of magnetic tape, transcribed in batches and then summarized and tagged with AI. Historical memory turned into a searchable archive.',
    ...mkImg('make-10-transcripciones') },
  { id:'n8n-09', p:'n8n', title:'Self-healing messaging system',
    desc:'Detects delivery errors and retries or reroutes the messages on its own, with no intervention.',
    img:null, workflow:'workflows/09-self-healing.json' },
  { id:'n8n-10', p:'n8n', title:'Duplicate user merging',
    desc:'Detects and merges duplicate records to keep a single profile per user.',
    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={'n8n workflow diagram: ' + 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={'Make automation: ' + 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} nodes · look inside</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={'View ' + 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={'View ' + 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={'Make automation: ' + 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="Close"><i data-lucide="x" /></button>
      <button className="lb__nav lb__nav--prev" onClick={(e)=>{e.stopPropagation();onStep(-1);}} aria-label="Previous"><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>Is this the automation for your business?</span>
            <a href={'https://wa.me/34623982404?text=' + encodeURIComponent('Hi! I\'m coming from your website. I\'m interested in the «' + a.title + '» automation for my business.')}
               target="_blank" rel="noopener">
              <i data-lucide="message-circle" />Get in touch
            </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="Next"><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','All','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">Automations</div>
          <h1>Repetitive work, <span className="grad">solved.</span></h1>
          <p>Automations running in businesses like yours. Each workflow connects the tools you already use and removes a task from your week. Filter by platform and look inside.</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 });
