// === scenes/presencia.jsx — PRESENCIA: quién más está tocando esta pantalla ahora mismo ===
// Compartido por la familia «histórico por campo»: lo cargan public/historico-por-campo/ y
// public/historico-reel/. Aquí NO hay ni un dato de ninguna pieza: nombres, fotos, colores y
// segundos entran por props.
//
// FIDELIDAD AL PRODUCTO (verificado el 13/08, no es licencia):
//   UserPresence.razor:19-29  → play YA pinta quién más está en la MISMA pantalla: recorre las
//                               sesiones vivas, pinta <FotoUsuarioU/> por cada una, las apila con
//                               un separador de margin-right:-33px y corta en MaxVisible = 4.
//   FotoUsuarioU.razor:12-24  → con foto: círculo de 40 px, border-radius 100%, borde fino gris.
//                               SIN foto: <DnzAvatarInitial/> — la inicial. Esa bifurcación es la
//                               que replica <PresAvatar/>, y por eso `foto` es opcional aquí.
//   El puntero teñido con la pastilla del nombre es la convención universal de presencia
//   (Figma, Docs, Miro). El producto no la tiene: es la traducción a vídeo de lo que UserPresence
//   dice con avatares apilados — «no estás solo en este registro».
//
// COLOR: la rueda de Dinaup, que ya es la firma de la marca.
//   azul #00C1FF · verde #56E16A · amarillo #FFDC18 · rojo #FF6751
//   El puntero y el anillo del avatar van del color PURO. La pastilla del nombre va del mismo
//   color rebajado a una luminancia fija (ver PRES_LUM_PASTILLA): sin eso el blanco sobre el
//   amarillo #FFDC18 no se lee y la pieza se cae justo donde tiene que leerse de un vistazo.
//   Quien quiera el color crudo pasa `colorPastilla`.
//
// REGLAS DEL MOTOR QUE ESTE ARCHIVO CUMPLE:
//   · función pura del reloj t — sin Math.random, sin Date, sin useState/useEffect
//   · ningún NaN/undefined en el DOM: todo prop se sanea y todo número sale de clamp/lerp
//   · fuera del rango [entrada, salida+0.3] devuelve null: no deja restos en pantalla
//   · el <CineCursor/> del motor NO se toca ni se reimplementa: se tiñe por CSS (PRES_CSS)

// Rueda de Dinaup — a mano para que una escena pueda escribir PRES_RUEDA.azul y no un hex suelto.
const PRES_RUEDA = { azul:'#00C1FF', verde:'#56E16A', amarillo:'#FFDC18', rojo:'#FF6751' };

// Escala interna del puntero. 1.6 es el tamaño al que ya se leen los punteros de las dos piezas
// (escena-01 usa 1.5-1.7). Queda FIJA para que la proporción puntero↔pastilla no se pueda romper:
// el único mando de tamaño es `escala`, que escala el grupo entero.
const PRES_CUR_ESC = 1.6;

// Luminancia objetivo de la pastilla. 0.32 deja el blanco legible sobre los cuatro colores de la
// rueda manteniendo el tono: el azul baja poco, el amarillo baja a oro. Subirlo aclara y borra el
// texto; bajarlo apaga la marca.
const PRES_LUM_PASTILLA = 0.32;

const PRES_GRIS = '#94A3B8';   // color de reserva si a alguien no le pasan color

// El puntero del motor es relleno #fff con trazo #111. Se tiñe por variables CSS puestas en el
// contenedor: una sola regla estática, el color entra inline. El motor no se toca.
const PRES_CSS = '.pres-cursor svg path{fill:var(--pres-fill,#fff);stroke:var(--pres-stroke,#111);}';

// ── Color: utilidades deterministas (nada de random, nada de dependencias) ──
function presRGB(hex){
  const s = typeof hex === 'string' ? hex.trim() : '';
  const m = /^#?([0-9a-fA-F]{6})$/.exec(s);
  if (!m) return null;
  const n = parseInt(m[1], 16);
  return [ (n >> 16) & 255, (n >> 8) & 255, n & 255 ];
}
function presHex(rgb){
  const c = (v) => {
    const k = Math.round(clamp(v, 0, 255));
    return (k < 16 ? '0' : '') + k.toString(16);
  };
  return `#${c(rgb[0])}${c(rgb[1])}${c(rgb[2])}`;
}
// Color saneado: si no es un hex de 6 dígitos, gris. Así nunca entra `undefined` en un style.
function presColor(color){
  return presRGB(color) ? (color.charAt(0) === '#' ? color.trim() : `#${color.trim()}`) : PRES_GRIS;
}
// Luminancia relativa (WCAG) — la que decide cuánto hay que rebajar la pastilla.
function presLum(color){
  const rgb = presRGB(color);
  if (!rgb) return 0.5;
  const lin = (v) => { const s = v/255; return s <= 0.03928 ? s/12.92 : Math.pow((s + 0.055)/1.055, 2.4); };
  return 0.2126*lin(rgb[0]) + 0.7152*lin(rgb[1]) + 0.0722*lin(rgb[2]);
}
// Multiplica el color en sRGB hasta dejarlo en la luminancia objetivo. Conserva el tono.
function presAPlomo(color, objetivo){
  const rgb = presRGB(color);
  if (!rgb) return PRES_GRIS;
  const L = presLum(color);
  if (L <= objetivo) return presColor(color);
  const f = Math.pow(objetivo / L, 1/2.4);            // luminancia ≈ f^2.4 al escalar en sRGB
  return presHex([ rgb[0]*f, rgb[1]*f, rgb[2]*f ]);
}
// Mezcla hacia negro — trazo del puntero y segundo tono del degradado de la inicial.
function presOscuro(color, k){
  const rgb = presRGB(color);
  if (!rgb) return '#4B5563';
  const f = 1 - clamp(k, 0, 1);
  return presHex([ rgb[0]*f, rgb[1]*f, rgb[2]*f ]);
}

// ── Avatar de presencia: FOTO si la hay, INICIAL con degradado si no (FotoUsuarioU.razor:9-24) ──
function PresAvatar({ nombre, foto, color, tam = 40, grosor = 3.5 }){
  const col  = presColor(color);
  const ruta = (typeof foto === 'string' && foto !== '') ? foto : null;
  const base = ((typeof nombre === 'string' && nombre !== '') ? nombre : '?').charAt(0).toUpperCase();
  return (
    <span style={{
      width:tam, height:tam, flexShrink:0, borderRadius:'50%', boxSizing:'border-box',
      border:`${grosor}px solid ${col}`,
      boxShadow:'0 4px 12px rgba(15,23,42,0.28)',
      display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden',
      background: ruta
        ? `#E2E8F0 url(${ruta}) center/cover no-repeat`
        : `linear-gradient(135deg, ${col}, ${presOscuro(col, 0.32)})`,
    }}>
      {ruta ? null : (
        <span className="f-plex" style={{
          color:'#fff', fontWeight:600, fontSize:Math.round(tam*0.42), lineHeight:1,
          textShadow:'0 1px 2px rgba(15,23,42,0.35)',
        }}>{base}</span>
      )}
    </span>
  );
}

// =====================================================================================
// <CursorPresencia/> — el puntero de alguien que está trabajando en esta misma pantalla.
//
//   El puntero es el <CineCursor/> del motor (primitives.jsx:165) TEÑIDO del color de esa
//   persona. Pegada a él, una pastilla con su avatar (anillo del color) y su nombre en blanco.
//
//   t        — reloj de la escena
//   x, y     — PUNTA del puntero en el lienzo 1920×1080
//   nombre   — texto de la pastilla (y de la inicial si no hay foto)
//   foto     — ruta local, p. ej. '/_engine/assets/avatares/marta.jpg'; sin ella, inicial
//   color    — hex de 6 dígitos de la rueda; cualquier otra cosa cae a gris
//   entrada  — segundo en el que aparece (null ⇒ está desde el principio)
//   salida   — segundo en el que se va (null ⇒ no se va)
//   escala   — escala del grupo entero (puntero + pastilla)
//   zIndex   — por defecto 24: por encima del formulario y de los punteros fantasma
//   soloAvatar — sin pastilla de nombre: solo puntero + avatar (para el reel, donde no cabe)
// =====================================================================================
function CursorPresencia({
  t = 0,
  x = 0, y = 0,
  nombre = '',
  foto = null,
  color = PRES_RUEDA.azul,
  entrada = null,
  salida = null,
  escala = 1,
  zIndex = 24,
  soloAvatar = false,
  colorPastilla = null,
  colorTexto = '#fff',
  tamAvatar = 40,
  tamTexto = 21,
  estilo = null,
}){
  const tt = t ?? 0;

  // Fuera de rango no se pinta: un puntero que ya se fue no deja medio píxel.
  if (entrada !== null && entrada !== undefined && tt < entrada) return null;
  if (salida  !== null && salida  !== undefined && tt > salida + 0.3) return null;

  const col   = presColor(color);
  const trazo = presOscuro(col, 0.55);
  const past  = (typeof colorPastilla === 'string' && colorPastilla !== '')
    ? presColor(colorPastilla)
    : presAPlomo(col, PRES_LUM_PASTILLA);

  // Entrada: easeOutBack (ya rebasa) + un rebote corto amortiguado justo después de aterrizar.
  const p  = (entrada === null || entrada === undefined) ? 1 : clamp((tt - entrada)/0.42, 0, 1);
  const e  = Easing.easeOutBack(p);
  const escIn = lerp(0.55, 1, e);
  const opIn  = clamp(p*2.2, 0, 1);
  const bP   = (entrada === null || entrada === undefined) ? 1 : clamp((tt - entrada - 0.42)/0.5, 0, 1);
  const bote = (bP >= 1 || bP <= 0) ? 0 : Math.sin(bP*Math.PI*2) * 4 * (1 - bP);

  // Salida: fade de 0.3 s con una deriva de 6 px hacia arriba — se va, no se apaga.
  const out   = (salida === null || salida === undefined) ? 0 : clamp((tt - salida)/0.3, 0, 1);
  const opOut = 1 - out;

  const esc = escala * escIn * lerp(1, 0.94, out);
  const dy  = bote - 6*out;

  const nom = (typeof nombre === 'string') ? nombre : '';
  const conPastilla = !soloAvatar && nom !== '';

  return (
    <div style={{
      position:'absolute', left:x, top:y, width:0, height:0,
      transform:`translate(0px, ${dy}px) scale(${esc})`, transformOrigin:'0% 0%',
      opacity: opIn * opOut,
      zIndex, pointerEvents:'none',
      ...(estilo || {}),
    }}>
      <style>{PRES_CSS}</style>

      {/* El puntero del motor, teñido por variable CSS. x=0,y=0: la punta cae en (x, y). */}
      <div className="pres-cursor" style={{ '--pres-fill': col, '--pres-stroke': trazo }}>
        <CineCursor x={0} y={0} scale={PRES_CUR_ESC}/>
      </div>

      {/* La pastilla, colgando de la punta hacia abajo-derecha (convención universal).
          Sin nombre no hay pastilla: un rectángulo de color vacío no dice nada. */}
      <div style={{
        position:'absolute', left: 15*PRES_CUR_ESC, top: 21*PRES_CUR_ESC,
        display:'flex', alignItems:'center', gap: conPastilla ? 11 : 0,
        padding: conPastilla ? '5px 18px 5px 5px' : 0,
        borderRadius: 999,
        background: conPastilla ? past : 'transparent',
        boxShadow: conPastilla ? '0 8px 22px rgba(15,23,42,0.26)' : 'none',
        zIndex: 19,
      }}>
        <PresAvatar nombre={nom} foto={foto} color={col} tam={tamAvatar} grosor={3.5}/>
        {conPastilla ? (
          <span className="f-plex" style={{
            fontSize:tamTexto, fontWeight:600, color:colorTexto, whiteSpace:'nowrap',
            letterSpacing:'0.005em', textShadow:'0 1px 2px rgba(15,23,42,0.30)',
          }}>{nom}</span>
        ) : null}
      </div>
    </div>
  );
}

// =====================================================================================
// <PilaPresencia/> — «aquí hay tres personas»: los avatares apilados con solape, como los
// pinta UserPresence.razor:19-29 (separador de -33 px, tope de 4 = MaxVisible).
//
//   t        — reloj de la escena
//   personas — [{ nombre, foto, color }]; sin foto se pinta la inicial
//   x, y     — esquina superior izquierda de la pila en el lienzo
//   escala   — escala del grupo
//   entrada  — segundo en el que entra (null ⇒ está desde el principio); cascada de +0.09 s
//   maxVisible / solape / tam — la geometría REAL del producto por defecto (4 · -17 · 40).
//     El -17 no es un ajuste a ojo: UserPresence mete un separador con margin-right:-33px DENTRO
//     de un `d-flex gap-2`, así que entre dos avatares hay 8 − 33 + 8 = −17 px. Con −33 pelado los
//     retratos se comen unos a otros y solo asoma un filo de 7 px.
// =====================================================================================
function PilaPresencia({
  t = 0,
  personas = [],
  x = 0, y = 0,
  escala = 1,
  entrada = null,
  maxVisible = 4,
  solape = -17,
  tam = 40,
  grosor = 3.5,
  zIndex = 24,
  estilo = null,
}){
  const tt = t ?? 0;
  const todas = Array.isArray(personas) ? personas : [];
  const tope  = Math.max(1, Math.round(maxVisible));
  const lista = todas.slice(0, tope);
  if (lista.length === 0) return null;
  if (entrada !== null && entrada !== undefined && tt < entrada) return null;

  const sobran = todas.length - lista.length;

  return (
    <div style={{
      position:'absolute', left:x, top:y,
      display:'flex', alignItems:'center',
      transform:`scale(${escala})`, transformOrigin:'0% 50%',
      zIndex, pointerEvents:'none',
      ...(estilo || {}),
    }}>
      {lista.map((per, i) => {
        const q = per || {};
        const p = (entrada === null || entrada === undefined)
          ? 1 : clamp((tt - entrada - i*0.09)/0.4, 0, 1);
        const e = Easing.easeOutBack(p);
        return (
          <span key={`p${i}`} style={{
            position:'relative',
            marginLeft: i === 0 ? 0 : solape,
            zIndex: lista.length - i,          // el primero, encima: así apila el producto
            transform:`scale(${lerp(0.5, 1, e)})`,
            opacity: clamp(p*2.2, 0, 1),
            display:'flex',
          }}>
            <PresAvatar nombre={q.nombre} foto={q.foto} color={q.color} tam={tam} grosor={grosor}/>
          </span>
        );
      })}

      {sobran > 0 ? (
        <span className="f-plex" style={{
          // el «+N» va ENCIMA de la pila: si se mete debajo, el solape se come el «+» y se lee «2»
          position:'relative', marginLeft:solape, zIndex: lista.length + 1, boxSizing:'border-box',
          width:tam, height:tam, borderRadius:'50%',
          border:`${grosor}px solid #CBD5E1`, background:'#E2E8F0',
          color:'#475569', fontWeight:600, fontSize:Math.round(tam*0.36), lineHeight:1,
          display:'flex', alignItems:'center', justifyContent:'center',
          boxShadow:'0 4px 12px rgba(15,23,42,0.20)',
          opacity: (entrada === null || entrada === undefined)
            ? 1 : clamp((tt - entrada - lista.length*0.09)/0.4, 0, 1),
        }}>{`+${sobran}`}</span>
      ) : null}
    </div>
  );
}

// PRES_RUEDA sale EXPLÍCITAMENTE: las escenas escriben PRES_RUEDA.azul y hasta ahora solo
// funcionaba porque Babel-en-navegador degrada el `const` de arriba a un `var` global. Eso es un
// efecto colateral del transpilado, no un contrato: cualquier día que estos archivos se sirvan como
// módulos, el reel se cae con «PRES_RUEDA is not defined». Exportado, no depende de nada.
Object.assign(window, { CursorPresencia, PilaPresencia, PRES_RUEDA, PresAvatar });
