// === comun.jsx — piezas compartidas por las escenas de «Derechos de ejecución» ===
// Todo es función de t (el motor puede saltar a cualquier segundo): las reservas y las peticiones
// salen de una simulación determinista que se calcula una vez por escena (simular) y se lee por t.
//
// Dos relojes: t es el de la escena (lo lleva la voz); s es el de la simulación, en segundos
// «de verdad» del ejemplo. reloj([[t,s],...]) los une a tramos, para acelerar las esperas largas.

const AZUL = '#0085FF', TINTA = '#0F172A', GRIS = '#64748B', BORDE = '#E2E8F0', ROJO = '#EF4444', VERDE = '#10B981', NARANJA = '#FB8C00';
const VIAJE = 820;   // px por segundo de escena que recorre una petición

// ── Reloj de la simulación ──
function reloj(puntos){
  const simAt = (t) => {
    if (t <= puntos[0][0]) return puntos[0][1];
    for (let i = 1; i < puntos.length; i++) {
      const [t0, s0] = puntos[i-1], [t1, s1] = puntos[i];
      if (t <= t1) return s0 + (s1 - s0) * (t - t0) / (t1 - t0);
    }
    return puntos[puntos.length-1][1];
  };
  const tAt = (s) => {
    for (let i = 1; i < puntos.length; i++) {
      const [t0, s0] = puntos[i-1], [t1, s1] = puntos[i];
      if (s1 > s0 && s >= s0 && s <= s1) return t0 + (t1 - t0) * (s - s0) / (s1 - s0);
    }
    return null;
  };
  const velocidad = (t) => {
    for (let i = 1; i < puntos.length; i++) {
      const [t0, s0] = puntos[i-1], [t1, s1] = puntos[i];
      if (t >= t0 && t <= t1) return (s1 - s0) / (t1 - t0);
    }
    return 0;
  };
  return { simAt, tAt, velocidad };
}

// ── Simulación: reservas que ganan derechos y peticiones que los gastan, en orden ──
// reservas: { id: { gana (número, o función de s), max, nivel } } · apps: [{ id, pide:(s)=>por segundo, cadena:['clave','empresa'] }]
// Una petición entra si hay derecho en todas las reservas de su cadena, y entonces gasta uno en
// cada una. Si falta en alguna, se rechaza ahí y no gasta en ninguna (FrenoDeRitmoD.Consumir).
function simular({ reservas, apps, fin, dt = 0.005 }){
  const PASO = 0.05, niv = {}, muestras = {}, peticiones = [], acc = {};
  for (const id in reservas) { niv[id] = reservas[id].nivel; muestras[id] = [niv[id]]; }
  apps.forEach(a => acc[a.id] = 0.999);
  let sig = PASO;
  for (let s = 0; s <= fin; s += dt) {
    for (const id in reservas) { const g = reservas[id].gana; niv[id] = Math.min(reservas[id].max, niv[id] + (typeof g === 'function' ? g(s) : g) * dt); }
    apps.forEach(a => {
      acc[a.id] += a.pide(s) * dt;
      while (acc[a.id] >= 1) {
        acc[a.id] -= 1;
        const falla = a.cadena.find(id => niv[id] < 1);
        if (!falla) a.cadena.forEach(id => niv[id] -= 1);
        peticiones.push({ app: a.id, s, falla: falla || null });
      }
    });
    if (s >= sig) { for (const id in reservas) muestras[id].push(niv[id]); sig += PASO; }
  }
  const nivelEn = (id, s) => { const m = muestras[id]; return m[clamp(Math.floor(s / PASO), 0, m.length - 1)]; };
  return { peticiones, nivelEn };
}

// ── Una reserva: rejilla de derechos que se llena de abajo arriba ──
function Reserva({ x, y, cols, filas, sep, r, nivel, titulo, gana, nota, opacidad = 1, resalte = 0 }){
  const max = cols * filas, lleno = Math.floor(nivel + 1e-6), parcial = nivel - lleno;
  const puntos = [];
  for (let i = 0; i < max; i++) {
    const cx = (i % cols) * sep + sep / 2, cy = (filas - 1 - Math.floor(i / cols)) * sep + sep / 2;
    const f = i < lleno ? 1 : (i === lleno ? parcial : 0);
    puntos.push(
      <div key={i} style={{ position:'absolute', left:cx - r, top:cy - r, width:r*2, height:r*2, borderRadius:100, boxSizing:'border-box',
        border: f > 0 ? 'none' : `2px solid ${BORDE}`, background: f > 0 ? AZUL : 'transparent', opacity: f > 0 ? 0.25 + 0.75 * f : 1 }}/>
    );
  }
  return (
    <div style={{ position:'absolute', left:x, top:y, width:cols*sep, height:filas*sep, opacity:opacidad }}>
      <div style={{ position:'absolute', left:0, right:0, bottom:'100%', marginBottom:14, whiteSpace:'nowrap' }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', gap:16 }}>
          <div className="f-plex" style={{ fontSize:26, fontWeight:700, color:TINTA }}>{titulo}</div>
          <div className="f-mono" style={{ fontSize:34, fontWeight:700, color:TINTA, transform:`scale(${1 + 0.12 * resalte})`, transformOrigin:'right bottom' }}>
            {lleno}<span style={{ color:GRIS, fontWeight:500 }}> / {max}</span>
          </div>
        </div>
        {gana && <div className="f-mono" style={{ fontSize:19, fontWeight:600, color:AZUL, marginTop:4 }}>+{gana} cada segundo{nota}</div>}
      </div>
      <div style={{ position:'absolute', inset:-14, borderRadius:18, background:'#fff', border:`1px solid ${BORDE}`, boxShadow:'0 18px 44px rgba(15,23,42,0.06)' }}/>
      {puntos}
    </div>
  );
}

// ── Nodos: una aplicación y Dinaup ──
// El icono del bucle: la app que repite la misma petición sin parar (escenas 1 y 4).
function IconoBucle({ size = 18, color = '#C2410C' }){
  return <svg width={size} height={size} viewBox="0 0 24 24" fill="none"><path d="M20 12a8 8 0 1 1-2.34-5.66" stroke={color} strokeWidth="2.6" strokeLinecap="round"/><path d="M20 4v5h-5" stroke={color} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"/></svg>;
}

function NodoApp({ x, y, nombre, color, pide, rechazos, opacidad = 1, bucle = 0 }){
  return (
    <div style={{ position:'absolute', left:x - 150, top:y - 50, width:300, height:100, opacity:opacidad }}>
      {bucle > 0 && (
        <div className="f-plex" style={{ position:'absolute', bottom:'100%', left:0, marginBottom:10, display:'flex', alignItems:'center', gap:8, padding:'5px 14px', borderRadius:100,
          background:'#FFF7ED', border:`1px solid ${NARANJA}66`, color:'#C2410C', fontSize:18, fontWeight:700, opacity:bucle, transform:`translateY(${(1 - bucle) * 8}px)` }}>
          <IconoBucle/> en bucle
        </div>
      )}
      <div style={{ position:'absolute', inset:0, background:'#fff', borderRadius:16, border:`1px solid ${BORDE}`, boxShadow:'0 14px 34px rgba(15,23,42,0.07)', display:'flex', alignItems:'center', gap:16, padding:'0 22px' }}>
        <div style={{ width:16, height:16, borderRadius:100, background:color, flexShrink:0 }}/>
        <div>
          <div className="f-plex" style={{ fontSize:25, fontWeight:700, color:TINTA }}>{nombre}</div>
          <div className="f-mono" style={{ fontSize:18, color:GRIS, marginTop:4 }}>{pide}</div>
        </div>
      </div>
      {rechazos > 0 && (
        <div style={{ position:'absolute', top:'100%', left:0, right:0, marginTop:14, display:'flex', justifyContent:'center' }}>
          <div className="f-mono" style={{ padding:'6px 16px', borderRadius:100, background:'#FEF2F2', border:`1px solid ${ROJO}44`, fontSize:20, fontWeight:700, color:ROJO }}>429 × {rechazos}</div>
        </div>
      )}
    </div>
  );
}

function NodoDinaup({ x, y, atendidas }){
  return (
    <div style={{ position:'absolute', left:x - 90, top:y - 52, width:180, height:104 }}>
      <div style={{ position:'absolute', inset:0, background:'#fff', borderRadius:18, border:`1px solid ${BORDE}`, boxShadow:'0 14px 34px rgba(15,23,42,0.07)', display:'flex', alignItems:'center', justifyContent:'center' }}>
        <DinaupLogo size={28}/>
      </div>
      {atendidas > 0 && (
        <div className="f-mono" style={{ position:'absolute', top:'100%', left:0, right:0, marginTop:14, textAlign:'center', fontSize:20, fontWeight:700, color:VERDE }}>{atendidas} atendidas</div>
      )}
    </div>
  );
}

// ── Reloj del ejemplo: los segundos «de verdad» y si van acelerados ──
function RelojEjemplo({ x, y, s, rapido, opacidad = 1 }){
  return (
    <div className="f-mono" style={{ position:'absolute', left:x, top:y, opacity:opacidad, display:'flex', alignItems:'center', gap:12, fontSize:20, color:GRIS }}>
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="9" stroke={GRIS} strokeWidth="2"/><path d="M12 7 V12 L15 14" stroke={GRIS} strokeWidth="2" strokeLinecap="round"/></svg>
      <span style={{ color:TINTA, fontWeight:700, minWidth:150 }}>segundo {Math.floor(s)}</span>
      {rapido > 1.05 && <span style={{ padding:'4px 12px', borderRadius:100, background:'#F1F5F9', fontSize:17, fontWeight:600 }}>acelerado ×{Math.round(rapido)}</span>}
    </div>
  );
}

// ── Peticiones en vuelo ──
// ruta: puntos [app, puerta1, (puerta2), dinaup] · puertas: ids de reserva en el orden de la ruta.
function largo(a, b){ return Math.hypot(b[0] - a[0], b[1] - a[1]); }
function tramos(ruta){ const out = [0]; for (let i = 1; i < ruta.length; i++) out.push(out[i-1] + largo(ruta[i-1], ruta[i]) / VIAJE); return out; }
// Segundos de escena que tarda una petición en llegar a la puerta k de su ruta.
function llegada(ruta, k){ return tramos(ruta)[k]; }

// Con el reloj acelerado se pinta una de cada ×N por app: el chorro se lee igual y no satura.
// Dos rechazos a la vez en la misma puerta llevan un solo «429»: superpuestos no se leen.
function Peticiones({ t, sim, rel, rutas, colores, cadenas }){
  const out = [], vistas = {}, rotulos = [];
  sim.peticiones.forEach((p, n) => {
    const t0 = rel.tAt(p.s); if (t0 === null) return;
    const nApp = vistas[p.app] = (vistas[p.app] || 0) + 1, v = Math.round(rel.velocidad(t0));
    if (v > 1 && nApp % v !== 0) return;
    if (t < t0) return;
    const ruta = rutas[p.app], tr = tramos(ruta), e = t - t0;
    const kFalla = p.falla ? cadenas[p.app].indexOf(p.falla) + 1 : -1;   // índice de la puerta en la ruta
    const fin = kFalla > 0 ? tr[kFalla] + 0.7 : tr[tr.length - 1] + 0.25;
    if (e > fin) return;
    let x, y, op = 1, rojo = false;
    if (kFalla > 0 && e >= tr[kFalla]) {
      const k = (e - tr[kFalla]) / 0.7, g = ruta[kFalla];
      x = g[0]; y = g[1] + 46 * Easing.easeOutCubic(k); op = 1 - k; rojo = true;
    } else {
      let i = 1; while (i < tr.length - 1 && e > tr[i]) i++;
      const k = clamp((e - tr[i-1]) / (tr[i] - tr[i-1]), 0, 1), a = ruta[i-1], b = ruta[i];
      x = lerp(a[0], b[0], k); y = lerp(a[1], b[1], k);
      if (e > tr[tr.length - 1]) op = 1 - (e - tr[tr.length - 1]) / 0.25;
    }
    out.push(<div key={n} style={{ position:'absolute', left:x - 9, top:y - 9, width:18, height:18, borderRadius:100, background: rojo ? ROJO : colores[p.app], opacity:op, boxShadow: rojo ? 'none' : `0 0 0 4px ${colores[p.app]}22` }}/>);
    const tapa = rotulos.some(([rx, ry]) => Math.abs(rx - x) < 40 && Math.abs(ry - y) < 24);
    if (rojo && op > 0.05 && !tapa && rotulos.push([x, y])) out.push(<div key={'r'+n} className="f-mono" style={{ position:'absolute', left:x + 16, top:y - 12, fontSize:19, fontWeight:700, color:ROJO, opacity:op }}>429</div>);
  });
  return out;
}

// Cuántas peticiones de una app han llegado ya a su final (rechazo o Dinaup) en t.
function contar(t, sim, rel, rutas, cadenas, app, rechazadas){
  let n = 0;
  sim.peticiones.forEach(p => {
    if (p.app !== app || (!!p.falla) !== rechazadas) return;
    const t0 = rel.tAt(p.s); if (t0 === null) return;
    const ruta = rutas[p.app], tr = tramos(ruta);
    const k = p.falla ? cadenas[p.app].indexOf(p.falla) + 1 : tr.length - 1;
    if (t >= t0 + tr[k]) n++;
  });
  return n;
}

function Linea({ puntos, color = BORDE }){
  const d = 'M' + puntos.map(p => p.join(',')).join(' L');
  return <svg style={{ position:'absolute', inset:0, overflow:'visible' }} width="1920" height="1080"><path d={d} fill="none" stroke={color} strokeWidth="3" strokeDasharray="2 12" strokeLinecap="round"/></svg>;
}

// Frase grande al pie, la que la voz está diciendo (≤ 6 palabras, refuerza sin transcribir).
function Rotulo({ t, desde, hasta = 1e9, children, top = 930 }){
  const p = clamp((t - desde) / 0.6, 0, 1) * (1 - clamp((t - hasta) / 0.4, 0, 1)), e = Easing.easeOutCubic(p);
  if (p <= 0) return null;
  return <div className="f-plex" style={{ position:'absolute', left:0, right:0, top, textAlign:'center', fontSize:44, fontWeight:800, letterSpacing:'-0.03em', color:TINTA, opacity:e, transform:`translateY(${(1-e)*14}px)` }}>{children}</div>;
}

// Cabecera de escena: la DemoHeader del motor con el antetítulo legible (14 px no se lee en un móvil).
function Cabecera({ t, eyebrow, title }){
  const p = clamp(t / 0.5, 0, 1);
  return (
    <div style={{ position:'absolute', top:44, left:0, right:0, textAlign:'center', opacity:p, zIndex:5 }}>
      <div className="f-plex" style={{ fontSize:20, fontWeight:700, letterSpacing:'3px', color:AZUL }}>{eyebrow}</div>
      <h2 className="f-plex" style={{ fontSize:50, fontWeight:700, letterSpacing:'-0.03em', margin:'12px 0 0', color:TINTA }}>{title}</h2>
    </div>
  );
}

const aparece = (t, t0, d = 0.6) => Easing.easeOutCubic(clamp((t - t0) / d, 0, 1));

// Los dos objetivos del reto. La escena 1 los plantea; la 7 los da por cumplidos, cada uno con su ✓.
const OBJETIVOS = ['Ninguna clave frena a las demás.', 'Sabes lo que vas a pagar.'];
function Objetivos({ t, desde, top, cumplidos = null, escala = 1, opacidad = 1 }){
  return (
    <div style={{ position:'absolute', left:0, right:0, top, display:'flex', flexDirection:'column', alignItems:'center', gap:22, opacity:opacidad, transform:`scale(${escala})`, transformOrigin:'center top' }}>
      {OBJETIVOS.map((o, i) => {
        const e = aparece(t, desde[i], 0.7), ok = cumplidos ? aparece(t, cumplidos[i], 0.5) : 0;
        return (
          <div key={i} style={{ width:1040, boxSizing:'border-box', display:'flex', alignItems:'center', gap:26, padding:'26px 38px', background:'#fff', borderRadius:20, border:`1px solid ${BORDE}`,
            borderLeft:`6px solid ${ok > 0.5 ? VERDE : AZUL}`, boxShadow:'0 18px 44px rgba(15,23,42,0.06)', opacity:e, transform:`translateY(${(1 - e) * 16}px)` }}>
            {cumplidos && (
              <div style={{ width:54, height:54, borderRadius:100, flexShrink:0, background: ok > 0 ? VERDE : '#F1F5F9', display:'flex', alignItems:'center', justifyContent:'center', transform:`scale(${0.8 + 0.2 * Easing.easeOutBack(ok)})` }}>
                <svg width="28" height="28" viewBox="0 0 24 24" fill="none" style={{ opacity:ok }}><path d="M5 12.5 L10 17.5 L19 7" stroke="#fff" strokeWidth="3.2" strokeLinecap="round" strokeLinejoin="round"/></svg>
              </div>
            )}
            <div className="f-plex" style={{ fontSize:46, fontWeight:800, letterSpacing:'-0.03em', color:TINTA }}>{o}</div>
          </div>
        );
      })}
    </div>
  );
}

Object.assign(window, { AZUL, TINTA, GRIS, BORDE, ROJO, VERDE, NARANJA, reloj, simular, Reserva, NodoApp, NodoDinaup, RelojEjemplo, Peticiones, contar, llegada, Linea, Rotulo, Cabecera, aparece, IconoBucle, Objetivos });
