/**
 * O sino das notificações, no cabeçalho, para o cliente e para a equipa (9 Out, pedido do Ricardo).
 *
 * Não inventa uma fonte nova: lê as filas que já existem (lib/fila.js), a do cliente
 * (`/api/:slug/fila`: o que está à espera dele, as novidades, o que espera pela NetScreen e o que a app
 * está a tratar) e a da equipa (`/api/admin/fila`, o «A tratar»). O número conta só o que é novo para
 * esta pessoa; o «visto» guarda-se na base (migração 063), e não no browser, para valer em qualquer
 * computador. Abrir o sino marca como visto o que lá está.
 *
 * Sem bundler — este ficheiro define um global. Ver a ordem em public/index.html.
 */

/** O que o sino mostra, de uma resposta da fila: `{ id, grupo, titulo, detalhe, conta, rota, quando, novidade }`. */
function notificacoesDaFila(fila, { equipa = false, slug = null } = {}) {
  // A identidade tem âmbito (a conta, ou a equipa e a conta do item) e a revisão que o servidor dá
  // (`rev`), ou a data: o «kit» visto numa conta silenciava o de outra, e um facto novo, com o mais antigo
  // ainda pendente, não contava como novo (parecer do Codex, 9 Out).
  const ambito = (x) => (equipa ? `equipa:${x.slug || ''}` : `conta:${slug || ''}`);
  const id = (x) => `${ambito(x)}|${x.chave}|${x.rev || x.desde || ''}`;
  if (equipa) {
    return (fila.itens || []).map((x) => ({
      id: id(x), grupo: 'tua', titulo: x.texto, detalhe: x.motivo || null, conta: x.conta || null, rota: x.rota || '/admin/fila', quando: x.desde || null, novidade: true,
    }));
  }
  return [
    ...(fila.novidades || []).map((x) => ({ id: id(x), grupo: 'novidade', titulo: x.texto, detalhe: null, rota: x.rota, quando: x.desde || null, novidade: true })),
    ...(fila.cliente || []).map((x) => ({ id: id(x), grupo: 'tua', titulo: x.texto, detalhe: null, rota: x.rota, quando: x.desde || null, novidade: true })),
    // Numa agência, o que espera por ela nos clientes, com o nome de cada um (agentes, 20 Out).
    ...(fila.clientes || []).map((x) => ({ id: id(x), grupo: 'tua', titulo: x.texto, detalhe: null, conta: x.conta || null, rota: x.rota, quando: x.desde || null, novidade: true })),
    // O perfil Leitura (becos de 9 Out, ponto 15): o que é do cliente vê-se, mas não é com ele, e não conta.
    ...(fila.responsavel || []).map((x) => ({ id: id(x), grupo: 'responsavel', titulo: x.texto, detalhe: null, conta: x.conta || null, rota: x.rota || null, quando: x.desde || null, novidade: false })),
    ...(fila.netscreen || []).map((x) => ({ id: id(x), grupo: 'netscreen', titulo: x.texto, detalhe: null, rota: x.rota || null, quando: x.desde || null, novidade: false })),
    ...(fila.sistema || []).map((x) => ({ id: id(x), grupo: 'sistema', titulo: x.texto, detalhe: null, rota: null, quando: x.desde || null, novidade: false })),
  ];
}

const GRUPOS_DO_SINO = {
  tua: { cliente: 'À tua espera', equipa: 'A tratar' },
  novidade: { cliente: 'Novidades', equipa: 'Novidades' },
  responsavel: { cliente: 'Com o responsável da conta', equipa: 'Com o responsável da conta' },
  netscreen: { cliente: 'Com a NetScreen', equipa: 'Com a NetScreen' },
  sistema: { cliente: 'A app está a tratar', equipa: 'A app está a tratar' },
};

/** «há 5 min», «há 3 h», «há 2 dias». */
function haQuanto(quando) {
  if (!quando) return null;
  const s = Math.max(0, (Date.now() - new Date(quando).getTime()) / 1000);
  if (s < 3600) return `há ${Math.max(1, Math.round(s / 60))} min`;
  if (s < 86400) return `há ${Math.round(s / 3600)} h`;
  const d = Math.round(s / 86400);
  return `há ${d} ${d === 1 ? 'dia' : 'dias'}`;
}

function Sino({ slug = null, equipa = false, onNavigate }) {
  const [itens, setItens] = React.useState(null);
  const [vistas, setVistas] = React.useState(() => new Set());
  const [aberto, setAberto] = React.useState(false);
  // As que eram novas quando se abriu: guardam o ponto enquanto a lista está aberta, para se ver quais
  // eram, embora já contem como vistas.
  const [novasAoAbrir, setNovasAoAbrir] = React.useState(() => new Set());
  // A última leitura falhou (a lista, se houver, é a de antes); e a marcação de visto que não ficou guardada.
  const [falhou, setFalhou] = React.useState(false);
  const [vistoFalhou, setVistoFalhou] = React.useState(false);
  const raiz = React.useRef(null);
  const lerRef = React.useRef(() => {});
  const url = equipa ? '/api/admin/fila' : (slug ? `/api/${slug}/fila` : null);

  // A fila e o que já se viu; outra vez de 30 em 30 s, ao voltar à janela, e quando o ecrã muda a fila.
  // Uma leitura de cada vez, e só a mais recente entra (Codex, 9 Out). Uma falha diz-se, com «Tentar
  // outra vez»; antes ficava «A ler as notificações» enquanto as leituras falhassem.
  React.useEffect(() => {
    if (!url) return undefined;
    let vivo = true;
    let aLer = false;
    let vez = 0;
    setItens(null);
    setFalhou(false);
    const ler = () => {
      if (document.visibilityState !== 'visible' || aLer) return;
      aLer = true;
      const esta = ++vez;
      Promise.all([api.get(url), api.get('/api/notificacoes/vistas')])
        .then(([fila, v]) => {
          if (!vivo || esta !== vez) return;
          setItens(notificacoesDaFila(fila, { equipa, slug }));
          setVistas(new Set(v.vistas || []));
          setFalhou(false);
        })
        .catch(() => { if (vivo && esta === vez) setFalhou(true); })
        .finally(() => { aLer = false; });
    };
    lerRef.current = ler;
    ler();
    const t = setInterval(ler, 30000);
    document.addEventListener('visibilitychange', ler);
    window.addEventListener('fila-mudou', ler);
    return () => { vivo = false; clearInterval(t); document.removeEventListener('visibilitychange', ler); window.removeEventListener('fila-mudou', ler); };
  }, [url, equipa, slug]);

  // Fecha com Escape e com um clique fora.
  React.useEffect(() => {
    if (!aberto) return undefined;
    const tecla = (e) => { if (e.key === 'Escape') setAberto(false); };
    const fora = (e) => { if (raiz.current && !raiz.current.contains(e.target)) setAberto(false); };
    document.addEventListener('keydown', tecla);
    document.addEventListener('mousedown', fora);
    return () => { document.removeEventListener('keydown', tecla); document.removeEventListener('mousedown', fora); };
  }, [aberto]);

  if (!url) return null;
  const lista = itens || [];
  const novas = lista.filter((n) => n.novidade && !vistas.has(n.id));

  const abrir = () => {
    const vaiAbrir = !aberto;
    setAberto(vaiAbrir);
    // Abrir marca como visto o que lá está: o número volta a zero até haver coisa nova.
    if (vaiAbrir) setNovasAoAbrir(new Set(novas.map((n) => n.id)));
    if (vaiAbrir && novas.length) {
      const ids = novas.map((n) => n.id);
      setVistas((v) => new Set([...v, ...ids]));
      setVistoFalhou(false);
      // Em lotes de 200, o limite da rota. Se não ficar guardado, o número volta, e diz-se porquê, em vez
      // de desaparecer e reaparecer na leitura seguinte sem explicação (Codex, 9 Out).
      const lotes = [];
      for (let i = 0; i < ids.length; i += 200) lotes.push(ids.slice(i, i + 200));
      Promise.all(lotes.map((chaves) => api.post('/api/notificacoes/vistas', { chaves }))).catch(() => {
        setVistas((v) => { const n = new Set(v); ids.forEach((x) => n.delete(x)); return n; });
        setVistoFalhou(true);
      });
    }
  };
  const ir = (n) => {
    setAberto(false);
    if (n.rota) onNavigate(n.rota);
  };

  const grupos = ['tua', 'novidade', 'responsavel', 'netscreen', 'sistema']
    .map((g) => [g, lista.filter((n) => n.grupo === g)])
    .filter(([, l]) => l.length);
  const verTudo = equipa ? '/admin/fila' : `/${slug}/dashboard`;

  return (
    <div ref={raiz} className="relative ml-auto shrink-0">
      <button
        type="button"
        onClick={abrir}
        aria-haspopup="dialog"
        aria-expanded={aberto}
        aria-label={novas.length ? `Notificações: ${novas.length} ${novas.length === 1 ? 'nova' : 'novas'}` : 'Notificações'}
        className="relative flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-secondary hover:text-foreground focus-visible:ring-1 focus-visible:ring-accent"
      >
        <IconSino size={20} />
        {novas.length > 0 && (
          <span className="absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-semibold leading-none text-primary-foreground">
            {novas.length > 99 ? '99+' : novas.length}
          </span>
        )}
      </button>

      {aberto && (
        <div
          role="dialog"
          aria-label="Notificações"
          className="fixed inset-x-2 top-16 z-50 max-h-[75vh] overflow-y-auto rounded-md border border-border bg-card shadow-2xl sm:absolute sm:inset-x-auto sm:right-0 sm:top-11 sm:w-[26rem]"
        >
          <div className="flex items-center justify-between border-b border-border px-4 py-3">
            <p className="text-sm font-medium text-foreground">Notificações</p>
            <button type="button" onClick={() => ir({ rota: verTudo })} className="text-xs text-muted-foreground underline-offset-2 hover:text-foreground hover:underline">
              {equipa ? 'Abrir o «A tratar»' : 'Ver no início'}
            </button>
          </div>
          {itens === null && !falhou && <AProcessar className="px-4 py-4" texto="A ler as notificações" />}
          {falhou && (
            <div className="flex items-center justify-between gap-3 border-b border-border px-4 py-3 text-xs" role="status">
              <span className="text-accent">{itens === null ? 'Não deu para ler as notificações.' : 'Não deu para actualizar: esta é a lista de antes.'}</span>
              <button type="button" onClick={() => lerRef.current()} className="shrink-0 underline-offset-2 hover:underline">Tentar outra vez</button>
            </div>
          )}
          {vistoFalhou && (
            <p className="border-b border-border px-4 py-2 text-xs text-accent" role="status">Não deu para guardar que as viste: voltam a contar como novas.</p>
          )}
          {itens !== null && !lista.length && (
            <p className="px-4 py-6 text-center text-sm text-muted-foreground">Nada à tua espera. Quando houver, aparece aqui.</p>
          )}
          {grupos.map(([g, l]) => (
            <section key={g} className="border-b border-border last:border-b-0">
              <p className="px-4 pb-1 pt-3 text-[11px] uppercase tracking-wider text-muted-foreground">{GRUPOS_DO_SINO[g][equipa ? 'equipa' : 'cliente']}</p>
              <ul>
                {l.map((n) => {
                  const nova = novasAoAbrir.has(n.id) || (n.novidade && !vistas.has(n.id));
                  const conteudo = (
                    <>
                      <span className="flex items-start gap-2">
                        {nova && <span className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-primary" aria-label="nova" />}
                        <span className="min-w-0">
                          {n.conta && <span className="block text-[11px] text-muted-foreground">{n.conta}</span>}
                          {/* As frases da fila continuam outra frase noutros ecrãs («a rever 2…»): aqui abrem. */}
                          <span className="block text-sm text-foreground">{String(n.titulo || '').charAt(0).toUpperCase() + String(n.titulo || '').slice(1)}</span>
                          {n.detalhe && <span className="mt-0.5 block text-xs leading-relaxed text-muted-foreground">{n.detalhe}</span>}
                          {n.quando && <span className="mt-0.5 block text-[11px] text-muted-foreground">{haQuanto(n.quando)}</span>}
                        </span>
                      </span>
                    </>
                  );
                  return (
                    <li key={n.id}>
                      {n.rota ? (
                        <button type="button" onClick={() => ir(n)} className="block w-full px-4 py-2.5 text-left outline-none hover:bg-secondary focus-visible:bg-secondary">
                          {conteudo}
                        </button>
                      ) : (
                        <div className="px-4 py-2.5">{conteudo}</div>
                      )}
                    </li>
                  );
                })}
              </ul>
            </section>
          ))}
        </div>
      )}
    </div>
  );
}
