// site-jogador/js/mar-de-nomes.jsx
//
// Mar de nomes dos doadores, correndo da esquerda pra direita ATRAS da secao das
// METAS DO HOST (meta-crowdfunding.jsx) — sao eles que empurram aquelas barras.
// Camada puramente decorativa: nao recebe clique, nao entra na ordem de leitura,
// e se a fonte de dados falhar ela simplesmente nao existe.
//
// O TAMANHO DO NOME CRESCE COM A DOACAO — mas o site NUNCA ve o valor em R$.
// A RPC public_donaters() devolve {nome, peso} com peso de 1 a 5, e o balde e
// calculado dentro do banco (supabase/migrations/20260826200000_public_donaters.sql).
// Quem abrir o DevTools ve o balde, nunca a quantia.
//
// Quem doou pouco NAO fica pequeno: o degrau 1 ja nasce em 20px (PISO abaixo).
//
// Quando alguem doa mais e sobe de degrau, o <span> daquela pessoa e o MESMO no
// do DOM (a lista e keyed por nome) — entao a fonte cresce por transition, sem
// reiniciar a travessia. O nome aumenta na cara do jogador, ao vivo.

const MAR_MAX = 60;            // nomes na tela ao mesmo tempo
const MAR_LANES = 14;          // faixas horizontais
const MAR_POLL_MS = 60000;     // mesmo ritmo da barra de arrecadacao
const MAR_CICLO_MS = 120000;   // so entra em acao se passar de MAR_MAX doadores

// peso 1..5 -> como o nome aparece. O indice 0 e o PISO.
// Os px sao um pouco maiores que na versao em mono: Rajdhani e CONDENSADA e
// renderiza menor no mesmo tamanho de fonte.
const MAR_ESCALA = [
  { px: 22, opacidade: 0.11, seg: 30 },
  { px: 28, opacidade: 0.15, seg: 37 },
  { px: 35, opacidade: 0.19, seg: 44 },
  { px: 42, opacidade: 0.24, seg: 51 },
  { px: 50, opacidade: 0.30, seg: 58 },
];

// FNV-1a: mesmo nome -> mesmo numero, sempre. E o que faz a pessoa nascer sempre
// na mesma faixa em vez de pular de lugar a cada re-render do React.
function marHash(s) {
  let h = 2166136261;
  for (let i = 0; i < s.length; i++) {
    h ^= s.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}

// Monta a posicao/tempo de cada nome. Deterministico: a mesma lista devolve
// sempre o mesmo arranjo.
function marArranjar(lista) {
  const comHash = lista.map((d) => {
    const h = marHash(d.nome);
    const faixa = MAR_ESCALA[Math.min(Math.max(d.peso, 1), 5) - 1];
    return {
      nome: d.nome,
      peso: d.peso,
      faixa,
      lane: h % MAR_LANES,
      // nome maior anda mais devagar: da profundidade, como parallax
      dur: faixa.seg + ((h >>> 8) % 13) - 6,
      jitter: ((h >>> 16) % 7) - 3, // desencosta da linha da faixa
      x: (h >>> 20) % 90,           // usado so quando a animacao esta desligada
    };
  });

  // Espalha os atrasos DENTRO de cada faixa: dois nomes na mesma linha saindo
  // juntos leem como bug. O atraso e NEGATIVO de proposito — assim a animacao
  // ja comeca no meio e a tela nasce povoada, em vez de vazia por 30 segundos.
  const porLane = new Map();
  for (const n of comHash) {
    if (!porLane.has(n.lane)) porLane.set(n.lane, []);
    porLane.get(n.lane).push(n);
  }
  for (const grupo of porLane.values()) {
    grupo.sort((a, b) => (a.nome < b.nome ? -1 : 1));
    grupo.forEach((n, i) => { n.delay = -(n.dur * i) / grupo.length; });
  }
  return comHash;
}

const MAR_CSS = `
@keyframes gzMarNome {
  from { transform: translateX(calc(-100% - 40px)); }
  to   { transform: translateX(calc(100vw + 40px)); }
}
.gz-mar {
  position: absolute; inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  contain: layout paint style;
}
.gz-mar b {
  position: absolute;
  left: 0;
  /* Rajdhani (a fonte dos titulos), nao a mono dos rotulos: os nomes sao gente,
     nao dado de terminal. Peso 700 pra dar presenca mesmo com opacidade baixa,
     e menos tracking que a mono porque a Rajdhani ja e condensada. */
  font-family: 'Rajdhani', sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
  color: var(--accent, #a8ff00);
  will-change: transform;
  animation-name: gzMarNome;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  /* e este transition que faz o nome CRESCER na tela quando a pessoa doa mais */
  transition: font-size .8s ease, opacity .8s ease;
}
.gz-mar--pausado b { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .gz-mar b {
    animation: none;
    transform: translateX(var(--gz-x));
  }
}
`;

function MarDeNomes() {
  const [nomes, setNomes] = React.useState([]);
  const [ciclo, setCiclo] = React.useState(0);
  const [visivel, setVisivel] = React.useState(true);
  const ref = React.useRef(null);

  // ── doadores (nome + peso). Mesmo ritmo da barra: quem doar aparece em ate 1 min.
  React.useEffect(() => {
    if (!window.sb) return;
    let vivo = true;
    const carregar = () => window.sb.rpc('public_donaters').then(({ data, error }) => {
      if (!vivo || error || !Array.isArray(data)) return;
      setNomes(data.filter((d) => d && d.nome));
    }).catch(() => {});
    carregar();
    const t = setInterval(carregar, MAR_POLL_MS);
    return () => { vivo = false; clearInterval(t); };
  }, []);

  // ── nao queima bateria com a secao fora da tela
  React.useEffect(() => {
    const el = ref.current;
    if (!el || typeof IntersectionObserver === 'undefined') return;
    const obs = new IntersectionObserver(([e]) => setVisivel(e.isIntersecting), { rootMargin: '120px' });
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  // ── rodizio: so liga se um dia passarmos de MAR_MAX doadores, pra que todo
  //    mundo apareca em algum momento em vez de o excedente sumir pra sempre.
  const excede = nomes.length > MAR_MAX;
  React.useEffect(() => {
    if (!excede) return;
    const t = setInterval(() => setCiclo((c) => c + 1), MAR_CICLO_MS);
    return () => clearInterval(t);
  }, [excede]);

  const arranjo = React.useMemo(() => {
    if (!nomes.length) return [];
    if (!excede) return marArranjar(nomes);
    const ini = (ciclo * MAR_MAX) % nomes.length;
    const janela = nomes.slice(ini, ini + MAR_MAX);
    if (janela.length < MAR_MAX) janela.push(...nomes.slice(0, MAR_MAX - janela.length));
    return marArranjar(janela);
  }, [nomes, ciclo, excede]);

  // sem dado = sem camada. Nunca meio-renderizado, nunca layout quebrado.
  if (!arranjo.length) return null;

  return (
    <div
      ref={ref}
      className={'gz-mar' + (visivel ? '' : ' gz-mar--pausado')}
      aria-hidden="true"
      data-mar-nomes={arranjo.length}
    >
      <style>{MAR_CSS}</style>
      {arranjo.map((n) => (
        <b
          key={n.nome}
          data-peso={n.peso}
          style={{
            top: `calc(${(n.lane * 100) / MAR_LANES}% + ${n.jitter}px)`,
            fontSize: n.faixa.px,
            opacity: n.faixa.opacidade,
            animationDuration: `${n.dur}s`,
            animationDelay: `${n.delay}s`,
            '--gz-x': `${n.x}vw`,
          }}
        >
          {n.nome}
        </b>
      ))}
    </div>
  );
}

Object.assign(window, { MarDeNomes });
