/**
 * "Ficheiros" — o separador de "A minha marca" onde entra tudo: ficheiros e o site.
 *
 * Até 24 Set eram dois ecrãs: a Biblioteca (o que existe) e as Fontes (o que se
 * leu), cada um com a sua zona de envio e regras de leitura diferentes. No
 * ensaio, o mesmo ficheiro aparecia em dois menus e não se percebia onde tinham
 * ficado as imagens. Avaliação de 24 Set e parecer do Codex: **um sítio para
 * encontrar ficheiros e sites, com o estado em cada linha**. O armazém único
 * (D14) fica igual; muda o ecrã.
 *
 * Segunda ronda de 24 Set (revisor da simplicidade e cliente simulado): o que
 * acabou de chegar aparece aqui marcado "Novo", em vez de num recibo à parte;
 * o que o ficheiro é lê-se como texto, com "Mudar"; o estado é uma frase, e
 * não as quatro etapas (essas ficam no back office); e cada linha tem uma
 * acção à vista, com o resto em "Mais".
 *
 * Três operações que não são a mesma, cada uma no sítio dela:
 * - **Deixar de ler** tira o que o agente aprendeu com o ficheiro; o ficheiro fica.
 * - **Apagar** tira o ficheiro do armazém, e a leitura com ele.
 * - **Retirar de uma campanha** é na campanha, e não aqui.
 */

const FILTROS_FICHEIROS = [
  ['todos', 'Tudo'],
  ['logotipo', 'Logótipos'],
  ['manual', 'Manuais'],
  ['imagem', 'Imagens'],
  ['documento', 'Documentos'],
  ['outro', 'Outros'],
  ['site', 'Sites'],
];

/**
 * Apagar um ficheiro: diz o que vai acontecer, em vez de perguntar se há
 * certeza. O ficheiro sai do armazém e não volta. Quem só quer que o agente
 * deixe de o ler tem a alternativa ali mesmo.
 */
function ConfirmarApagarFicheiro({ material, fonte, ocupado, onFechar, onConfirmar, onDeixarDeLer }) {
  if (!material) return null;
  return (
    <Modal
      aberto
      onFechar={onFechar}
      titulo="Apagar este ficheiro"
      acoes={
        <>
          <button
            type="button"
            onClick={onFechar}
            data-foco-inicial
            className="border border-border px-4 py-2 text-sm uppercase tracking-wider text-muted-foreground outline-none hover:text-foreground focus-visible:ring-1 focus-visible:ring-accent"
          >
            Deixar ficar
          </button>
          <button
            type="button"
            disabled={ocupado}
            onClick={() => onConfirmar(material)}
            className="bg-primary px-4 py-2 text-sm uppercase tracking-wider text-primary-foreground outline-none hover:bg-primary/90 focus-visible:ring-1 focus-visible:ring-accent disabled:bg-secondary disabled:text-muted-foreground"
          >
            Apagar
          </button>
        </>
      }
    >
      <p className="text-sm text-foreground">{nomeAVista(material.original_name)}</p>
      {/* O logótipo do kit diz-se (agentes, 20 Out: apagava-se o de fundos escuros sem saber que estava em uso). */}
      {material.no_kit && (
        <p className="mt-3 border-l-2 border-accent pl-3 text-sm leading-relaxed text-foreground">
          {`É o logótipo ${material.no_kit === 'negativo' ? 'para fundos escuros ' : ''}guardado na tua identidade visual. As campanhas deixam de o usar até escolheres outro em Identidade, ou voltares a carregar este mesmo ficheiro.`}
        </p>
      )}
      <ul className="mt-3 space-y-1.5 text-sm leading-relaxed text-muted-foreground">
        <li>O ficheiro sai do nosso armazenamento e não se recupera.</li>
        {fonte && <li>O que o agente aprendeu com ele deixa de contar.</li>}
        {/* O que acontece às campanhas aprovadas e no ar (quinto parecer do Codex, 3 Out). */}
        <li>Campanhas aprovadas ou no ar que o usem mantêm as imagens já fixadas e o que já foi entregue.</li>
        <li>Um vídeo ainda por compor que o use fica parado até voltares a carregar o mesmo ficheiro.</li>
        <li>Campanhas por aprovar que o usem ficam sem ele: escolhe outra fotografia antes de as aprovar.</li>
      </ul>
      {fonte && (
        <button type="button" disabled={ocupado} onClick={() => onDeixarDeLer(fonte)} className="mt-3 text-sm text-foreground underline underline-offset-2 hover:text-primary">
          Só quero que o agente deixe de o ler (o ficheiro fica)
        </button>
      )}
    </Modal>
  );
}

/** O estado de um ficheiro numa frase, dito a quem é da marca. */
function estadoDoFicheiro(material, fonte, parado) {
  if (material.campanha_id) return { texto: `Só da campanha «${material.campanha_titulo || 'sem título'}»` };
  if (!fonte) return null;
  if (fonte.status === 'review_pending') {
    return { texto: 'O texto desta imagem espera que o aproves', tua: true,
      motivo: 'Uma captura de um post pode trazer comentários de clientes: vê se o texto é mesmo da marca.' };
  }
  const l = material.leitura;
  if (!l) return null;
  // Ler e tirar o que interessa são duas fases, e diz-se em qual está (Codex, 9 Out: o «A ler…»
  // escondia o «lido, a tirar o que interessa»).
  if (parado && l.emCurso) {
    return l.aAnalisar
      ? { texto: 'Lido; a análise está parada', motivo: 'Continua assim que o processamento voltar. Não precisas de fazer nada.' }
      : { texto: 'Por ler: o processamento está parado', motivo: 'Vai ser lido assim que voltar. Não precisas de fazer nada.' };
  }
  if (l.emCurso) return { texto: l.aAnalisar ? 'Lido, a tirar o que interessa' : 'A ler…', aTrabalhar: true };
  if (l.bloqueado) return { texto: 'Lido; a análise parou', motivo: l.motivo, atencao: true };
  if (l.falhou) return { texto: l.texto.charAt(0).toUpperCase() + l.texto.slice(1), motivo: l.motivo, atencao: true };
  return { texto: l.texto === 'lido' ? 'Lido' : l.texto.charAt(0).toUpperCase() + l.texto.slice(1), motivo: l.parcial || l.analiseFalhou ? l.motivo : null };
}

/** Um ficheiro: o que é, o estado numa frase, uma acção à vista e o resto em "Mais". */
/**
 * O manual que vale, com dois ou mais: o ano no nome do ficheiro e, sem ele, o ano em que foi
 * carregado; com o mesmo ano, o último carregado. A mesma regra de lib/manualEmVigor.js (20 Out:
 * o da Pulso de 2024, carregado por último, mandava sobre o de 2026).
 */
function anoDoManual(m) {
  const a = String(m.original_name || '').match(/(?:^|\D)(20\d{2})(?!\d)/);
  return a ? Number(a[1]) : new Date(m.uploaded_at).getUTCFullYear();
}
function manualEmVigorPrimeiro(a, b) {
  return anoDoManual(b) - anoDoManual(a) || new Date(b.uploaded_at) - new Date(a.uploaded_at);
}

function LinhaFicheiro({ material, fonte, novo, manualAntigo, parado, podeMexer, ocupado, accoes, emGrelha = false }) {
  const [aMudar, setAMudar] = React.useState(false);
  const [mais, setMais] = React.useState(false);
  const ehImagem = material.kind === 'image';
  const ehLogo = ehImagem && (material.papel === 'logotipo' || material.detected_mime === 'image/svg+xml');
  const estado = estadoDoFicheiro(material, fonte, parado);
  // Tirado de um manual: diz-se de qual, e porque está na lista sem ter sido carregado (agentes, 20 Out).
  const paginaDoManualTirado = /-do-manual-p(\d+)\.svg$/.exec(material.original_name || '');
  const podeLer = !fonte && !material.campanha_id && material.detected_mime !== 'image/svg+xml' && material.papel !== 'logotipo';
  // "Ler outra vez" só quando falhou por uma razão que se resolve a repetir:
  // um PDF digitalizado dava o mesmo (cliente simulado, 24 Set).
  const podeReler = fonte && ['error', 'blocked'].includes(fonte.status) && fonte.tem_ficheiro;

  // A acção à vista: a que a situação pede, e só uma.
  let principal = null;
  if (podeMexer) {
    if (fonte && fonte.status === 'review_pending') principal = ['Aprovar o texto', () => accoes.aprovar(fonte), true];
    else if (material.campanha_id) principal = ['Disponibilizar na marca', () => accoes.darAMarca(material)];
    else if (podeReler) principal = ['Ler outra vez', () => accoes.reler(fonte)];
    else if (podeLer && !ehImagem) principal = ['Ler o texto', () => accoes.ler(material)];
  }

  return (
    // Em grelha (só imagens), cada ficheiro é um cartão com a miniatura maior (28 Set).
    <li className={emGrelha
      ? 'flex gap-3 rounded-md border border-border bg-card p-3'
      : 'flex gap-4 border-t border-border px-5 py-3.5 first:border-t-0'}>
      {/* Um logótipo inteiro e num fundo onde se veja; uma fotografia preenche o quadrado (agentes,
          20 Out: os logótipos cortados ao centro liam-se «pul» ou «lhar», e o claro sumia no escuro). */}
      <div
        className={`flex shrink-0 items-center justify-center overflow-hidden rounded-md ${ehLogo ? 'p-1.5' : 'bg-secondary'} ${emGrelha ? 'h-20 w-20' : 'h-14 w-14'}`}
        style={ehLogo ? { background: material.tom === 'claro' ? '#0b0b0f' : '#f4f4f5' } : undefined}
      >
        {ehImagem ? (
          <img src={material.url} alt={material.original_name} loading="lazy" className={`h-full w-full ${ehLogo ? 'object-contain' : 'object-cover'}`} />
        ) : (
          <span className="font-display text-sm uppercase tracking-wide text-muted-foreground">
            {(material.original_name.split('.').pop() || 'ficheiro').slice(0, 4)}
          </span>
        )}
      </div>

      <div className="min-w-0 grow">
        <p className="flex min-w-0 items-baseline gap-2">
          <span className="truncate text-sm text-foreground" title={material.original_name}>{nomeAVista(material.original_name)}</span>
          {novo && <span className="shrink-0 bg-[hsl(var(--primary)/0.15)] px-1.5 text-[10px] uppercase tracking-wider text-primary">Novo</span>}
          {/* Qual é o do kit (agentes, 20 Out: nada o indicava). */}
          {material.no_kit && (
            <span className="shrink-0 border border-border px-1.5 text-[10px] uppercase tracking-wider text-muted-foreground">
              {material.no_kit === 'negativo' ? 'Em uso: fundos escuros' : 'Em uso: logótipo'}
            </span>
          )}
        </p>
        {material.manual_nome && (
          <p className="mt-0.5 max-w-prose text-xs leading-relaxed text-muted-foreground">
            {`Tirado por nós do manual «${material.manual_nome}»${paginaDoManualTirado ? `, página ${paginaDoManualTirado[1]}` : ''}: por isso não conta entre os que carregaste.`}
          </p>
        )}
        <p className="mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-muted-foreground">
          {aMudar ? (
            <select
              autoFocus
              value={material.papel}
              disabled={ocupado}
              onChange={(e) => { setAMudar(false); accoes.mudarPapel(material, e.target.value); }}
              onBlur={() => setAMudar(false)}
              className="border border-border bg-background px-1.5 py-0.5 text-xs text-foreground"
              aria-label={`O que é ${material.original_name}`}
            >
              {PAPEIS_FICHEIRO.map(([v, t]) => <option key={v} value={v}>{t}</option>)}
            </select>
          ) : (
            <span>{NOME_PAPEL_FICHEIRO[material.papel] || material.papel}</span>
          )}
          {estado && <span aria-hidden="true">·</span>}
          {estado && (
            <span role={estado.aTrabalhar ? 'status' : undefined} className={`inline-flex items-center gap-1.5 ${estado.tua || estado.atencao ? 'text-accent' : ''}`}>
              {estado.aTrabalhar && <ATrabalhar className="text-primary" />}
              {estado.texto}
            </span>
          )}
        </p>
        {estado && estado.motivo && <p className="mt-1 max-w-prose text-xs leading-relaxed text-muted-foreground">{estado.motivo}</p>}
        {manualAntigo && (
          <p className="mt-1 max-w-prose text-xs leading-relaxed text-muted-foreground">
            {/* A regra à vista (20 Out): o ano no nome do ficheiro, e não a ordem em que foram lidos. */}
            Há um manual mais recente, pelo ano no nome do ficheiro (sem ano, pelo ano em que foi carregado): é esse que vale para as cores, a letra e como a marca fala. Se este já não serve, apaga-o; se é o outro que está errado, muda o que esse é.
          </p>
        )}

        {podeMexer && (
          <div className="mt-2 flex flex-wrap items-center gap-2">
            {principal && (
              <button
                type="button"
                disabled={ocupado}
                onClick={principal[1]}
                className={principal[2]
                  ? 'bg-primary px-3 py-1 text-xs text-primary-foreground hover:bg-primary/90 disabled:bg-secondary disabled:text-muted-foreground'
                  : 'border border-border px-3 py-1 text-xs disabled:opacity-50'}
              >
                {principal[0]}
              </button>
            )}
            {mais ? (
              <>
                <button type="button" onClick={() => { setAMudar(true); setMais(false); }} className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
                  Mudar o que é
                </button>
                {/* O ficheiro, para o guardar ou voltar a carregar (ensaio de 3 Out: não havia como). */}
                {material.url && (
                  <a href={`${material.url}?descarregar=1`} className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
                    Descarregar
                  </a>
                )}
                {podeLer && ehImagem && (
                  <button type="button" disabled={ocupado} onClick={() => accoes.ler(material)} className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
                    Tem texto? Ler o texto
                  </button>
                )}
                {/* Um ficheiro já lido (o manual, sobretudo) também se volta a ler: tira o logótipo e as
                    regras outra vez, e a versão anterior do logótipo sai (C-4, NESCAFÉ 30 Out). */}
                {fonte && fonte.tem_ficheiro && fonte.status === 'done' && (
                  <button type="button" disabled={ocupado} onClick={() => accoes.reler(fonte)} className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
                    Ler outra vez
                  </button>
                )}
                {fonte && (
                  <button type="button" disabled={ocupado} onClick={() => accoes.deixarDeLer(fonte)} className="text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground">
                    Deixar de ler
                  </button>
                )}
                <button type="button" disabled={ocupado} onClick={() => accoes.apagar(material)} className="text-xs text-muted-foreground underline underline-offset-2 hover:!text-[hsl(0_80%_70%)]">
                  Apagar
                </button>
              </>
            ) : (
              <button type="button" onClick={() => setMais(true)} aria-expanded="false" className="ns-btn ns-btn-sm ns-btn-fantasma h-7 px-2 text-xs">
                Mais
              </button>
            )}
          </div>
        )}
      </div>
    </li>
  );
}

function MarcaFicheiros({ slug, podeMexer = true, etapasPorFonte = null, onMudou = null }) {
  const [dados, setDados] = React.useState(null);
  const [erro, setErro] = React.useState(null);
  const [filtro, setFiltro] = React.useState('todos');
  const [ocupado, setOcupado] = React.useState(false);
  const [aApagar, setAApagar] = React.useState(null);
  const [comSite, setComSite] = React.useState(true);
  const [sitePorRecolher, setSitePorRecolher] = React.useState(null);
  const [aRecolherSite, setARecolherSite] = React.useState(false);
  const [siteAmbito, setSiteAmbito] = React.useState(null);
  const [novos, setNovos] = React.useState(() => new Set());
  const [toast, setToast] = React.useState(null);
  const fecharToast = React.useCallback(() => setToast(null), []);

  // Rejeita quando falha: a actualização automática diz que não deu.
  const lerTudo = React.useCallback(async () => {
    const [materiais, fontes, espaco, processamento] = await Promise.all([
      api.get(`/api/${slug}/materiais`),
      api.get(`/api/${slug}/kb/fontes`),
      api.get(`/api/${slug}/materiais/espaco`),
      api.get(`/api/${slug}/kb/processamento`).catch(() => null),
    ]);
    setDados({ materiais, fontes, espaco, processamento });
    setErro(null);
    // O topo de "A minha marca" vem do resumo: refaz-se.
    if (onMudou) onMudou();
  }, [slug, onMudou]);
  const carregar = React.useCallback(
    () => lerTudo().catch((e) => setErro(e.message || 'Não foi possível ler os ficheiros.')),
    [lerTudo]
  );

  React.useEffect(() => { setDados(null); setErro(null); setNovos(new Set()); carregar(); }, [carregar]);

  // Enquanto alguma coisa está a ser lida, de 8 em 8 segundos; e sempre que a
  // vez é de outra pessoa, de 30 em 30 (useActualizar).
  const aTrabalhar = Boolean(dados && dados.fontes.some((f) => ['pending', 'processing'].includes(f.status)));
  React.useEffect(() => {
    if (!aTrabalhar) return undefined;
    const t = setInterval(carregar, 8000);
    return () => clearInterval(t);
  }, [aTrabalhar, carregar]);
  useActualizar(lerTudo, { activo: !aTrabalhar && !aApagar && !ocupado });

  const recolherSite = React.useCallback(async (fonte) => {
    setARecolherSite(true);
    try {
      await api.post(`/api/${slug}/marca/site-identidade`, { fonte_id: fonte.id });
      await carregar();
      setToast({ id: Date.now(), mensagem: 'Referência visual recolhida. Revê as cores e o logótipo na proposta da marca.' });
    } catch (e) {
      if (['FONTE_OFERTA', 'FONTE_AMBITO_INCERTO'].includes(e.corpo?.codigo)) {
        setSiteAmbito({ ...e.corpo, pedido: { source_url: fonte.source_url, autorizo: true }, fonteId: fonte.id });
      }
      setToast({ id: Date.now(), mensagem: e.message || 'Não foi possível recolher a identidade do site. Podes tentar novamente ou acrescentar os materiais.', erro: true });
    } finally { setARecolherSite(false); }
  }, [slug, carregar]);
  React.useEffect(() => {
    if (!sitePorRecolher || aRecolherSite) return;
    const fonte = dados?.fontes.find((f) => f.id === sitePorRecolher);
    if (!fonte || ['pending', 'processing'].includes(fonte.status)) return;
    setSitePorRecolher(null);
    if (fonte.status === 'done') recolherSite(fonte);
  }, [dados, sitePorRecolher, aRecolherSite, recolherSite]);
  React.useEffect(() => { setSitePorRecolher(null); }, [slug]);

  // Cada ficheiro que chega entra na lista, marcado "Novo".
  const chegou = React.useCallback((r) => {
    // Um repetido não é novo: a faixa já diz «já existia» (agentes, 20 Out: subia ao topo como «Novo»).
    if (r && r.material && !r.existia) setNovos((s) => new Set(s).add(r.material.id));
    if (r && r.reposto_no_kit) setToast({ id: Date.now(), mensagem: `${r.material.original_name} voltou ao lugar que tinha na identidade visual.` });
    carregar();
  }, [carregar]);

  const comAviso = async (accao, mensagem) => {
    setOcupado(true);
    try {
      await accao();
      await carregar();
      if (mensagem) setToast({ id: Date.now(), mensagem });
    } catch (e) {
      setToast({ id: Date.now(), mensagem: e.message, erro: true });
    } finally {
      setOcupado(false);
    }
  };

  const accoes = {
    mudarPapel: (m, papel) => comAviso(() => api.patch(`/api/${slug}/materiais/${m.id}`, { papel }), null),
    darAMarca: (m) => comAviso(() => api.patch(`/api/${slug}/materiais/${m.id}`, { ambito: 'marca' }), 'Agora é da marca: aparece nas outras campanhas.'),
    ler: (m) => comAviso(() => api.post(`/api/${slug}/materiais/${m.id}/ler`, {}), 'Vai ser lido dentro de minutos.'),
    aprovar: (f) => comAviso(() => api.post(`/api/${slug}/kb/fontes/${f.id}/aprovar`, {}), 'Aprovado: o agente já o pode usar.'),
    reler: (f) => comAviso(() => api.post(`/api/${slug}/kb/fontes/${f.id}/reler`, {}), 'Vai ser lido outra vez.'),
    retomar: (f) => comAviso(() => api.post(`/api/${slug}/kb/fontes/${f.id}/retomar`, {}), 'Vai continuar de onde ficou.'),
    deixarDeLer: (f) => comAviso(async () => {
      await api.del(`/api/${slug}/kb/fontes/${f.id}`);
      setAApagar(null);
    }, 'O agente deixou de o ler. O ficheiro ficou.'),
    removerSite: (f) => comAviso(() => api.del(`/api/${slug}/kb/fontes/${f.id}`), 'Removido.'),
    apagar: (m) => setAApagar(m),
  };

  const apagar = (m) => comAviso(async () => {
    await api.del(`/api/${slug}/materiais/${m.id}`);
    setAApagar(null);
  }, m.no_kit ? 'Apagado. Escolhe outro logótipo em Identidade.' : 'Apagado.');

  const guardarSite = async (corpo, limpar) => {
    setOcupado(true); setSiteAmbito(null);
    try {
      const fonte = await api.post(`/api/${slug}/kb/fontes`, corpo);
      setSitePorRecolher(fonte.id); limpar(); setComSite(false);
      await carregar(); setToast({ id: Date.now(), mensagem: 'Referência da marca acrescentada. Vai ser lida dentro de minutos.' });
    } catch (e) {
      if (['FONTE_OFERTA', 'FONTE_AMBITO_INCERTO'].includes(e.corpo?.codigo)) setSiteAmbito({ ...e.corpo, pedido: corpo, limpar });
      else setToast({ id: Date.now(), mensagem: e.message, erro: true });
    } finally { setOcupado(false); }
  };
  const campanhaDoSite = siteAmbito ? `/${slug}/criar?referencia=${encodeURIComponent(siteAmbito.referenciaOferta?.url || siteAmbito.pedido.source_url)}&tema=${encodeURIComponent((siteAmbito.referenciaOferta?.nome || '').slice(0,80))}` : null;

  if (erro && !dados) {
    return (
      <div className="border border-border bg-card px-6 py-12 text-center">
        <p className="font-display text-xl tracking-wide">Não deu para ler os ficheiros</p>
        <p className="mt-2 text-sm text-muted-foreground">{erro}</p>
      </div>
    );
  }
  if (!dados) return <SkeletonTabela linhas={3} />;

  const fontePorMaterial = Object.fromEntries(dados.fontes.filter((f) => f.material_id).map((f) => [f.material_id, f]));
  const sites = dados.fontes.filter((f) => !f.material_id);
  const total = dados.materiais.length + sites.length;
  const conta = (chave) => (chave === 'todos' ? total
    : chave === 'site' ? sites.length : dados.materiais.filter((m) => m.papel === chave).length);
  // Os novos primeiro, e depois pela ordem de chegada (a do servidor).
  const materiais = (filtro === 'todos' ? dados.materiais
    : filtro === 'site' ? [] : dados.materiais.filter((m) => m.papel === filtro))
    .slice().sort((a, b) => novos.has(b.id) - novos.has(a.id));
  const sitesVisiveis = filtro === 'todos' || filtro === 'site' ? sites : [];
  // O manual que vale: o mais recente da marca (os de uma campanha não contam).
  // Só entre os lidos: é o que a proposta usa (lib/voz.js, textoDaMarca). Um manual
  // guardado sem ler não pode dizer ao antigo que já não vale (revisor, 24 Set).
  const manualActual = dados.materiais.filter((m) => m.papel === 'manual' && !m.campanha_id && fontePorMaterial[m.id] && fontePorMaterial[m.id].status === 'done')
    .sort(manualEmVigorPrimeiro)[0] || null;
  const parado = Boolean(dados.processamento && dados.processamento.parado);
  // A barra do espaço só quando começa a ser uma preocupação.
  const espaco = dados.espaco && dados.espaco.limite && dados.espaco.usado / dados.espaco.limite >= 0.8 ? dados.espaco : null;

  return (
    <div className="space-y-6">
      {!podeMexer && <SoQuemPode accao="carregar ou apagar ficheiros" />}
      {podeMexer && (
        <section className="border border-border bg-card p-5">
          <h2 className="ns-seccao-titulo">Começar pelo site</h2>
          <p className="mt-2 text-sm text-muted-foreground">Usa uma referência geral da marca. Uma página de produto, serviço ou promoção pertence à campanha e não muda a identidade da marca.</p>
          <div className="mt-4">
            {comSite ? <KbFormSite ocupado={ocupado || aRecolherSite} onGuardar={guardarSite} /> : <button type="button" onClick={() => setComSite(true)} className="text-sm underline underline-offset-2">Acrescentar outro site</button>}
          </div>
          {aRecolherSite && <p role="status" className="mt-3 text-sm text-muted-foreground">A recolher cores e logótipos do site…</p>}
          {siteAmbito && <div role="status" className="mt-4 space-y-3 border border-border p-4">
            <p className="text-sm font-medium">{siteAmbito.ambito === 'oferta' ? 'Esta página é de uma oferta' : 'Esta referência é da marca ou de uma campanha?'}</p>
            <p className="text-sm text-muted-foreground">{siteAmbito.ambito === 'oferta' ? 'Leva-a para a campanha. O nome, as cores e o logótipo gerais da marca mantêm-se.' : 'Escolhe como queres usar esta página. A referência só passa a definir a marca depois desta escolha e da revisão.'}</p>
            <div className="flex flex-wrap gap-3">
              <a href={campanhaDoSite} className="border border-border px-3 py-2 text-sm underline underline-offset-2">Usar na campanha</a>
              {siteAmbito.ambito === 'incerto' && !siteAmbito.fonteId && <button type="button" disabled={ocupado} onClick={() => guardarSite({ ...siteAmbito.pedido, finalidade: 'marca', ambito_confirmado: true }, siteAmbito.limpar)} className="border border-border px-3 py-2 text-sm disabled:opacity-50">É uma referência geral da marca</button>}
            </div>
            {siteAmbito.urlMarcaSugerida && <p className="break-all text-xs text-muted-foreground">Podes indicar outra página geral da marca. Sugestão por confirmar: {siteAmbito.urlMarcaSugerida}</p>}
          </div>}
          <div className="mt-5 border-t border-border pt-4">
          <h3 className="ns-seccao-titulo mb-3">Ou acrescentar ficheiros</h3>
          <EnvioFicheiros
            slug={slug}
            podeMexer={podeMexer}
            dica="O manual de normas, o logótipo, fotografias, catálogos. PDF, Word, PowerPoint, Excel, CSV, PNG, JPG, WebP e SVG. Podes largar vários de uma vez."
            onGuardado={chegou}
          />
          {espaco && <div className="mt-4 max-w-xs"><KbEspaco espaco={espaco} /></div>}
          </div>
        </section>
      )}

      <section>
        <div className="flex flex-wrap items-center justify-between gap-3">
          <h2 className="ns-seccao-titulo">O que já temos</h2>
          {/* Os filtros só quando há ficheiros que cheguem para precisar deles. */}
          {total >= 8 && (
            <div className="flex flex-wrap gap-1.5" role="group" aria-label="Mostrar">
              {FILTROS_FICHEIROS.filter(([k]) => k === 'todos' || conta(k) > 0).map(([k, t]) => (
                <button
                  key={k}
                  type="button"
                  aria-pressed={filtro === k}
                  onClick={() => setFiltro(k)}
                  className="ns-chip"
                >
                  {t} <span className="tabular-nums text-muted-foreground">{conta(k)}</span>
                </button>
              ))}
            </div>
          )}
        </div>

        {/* Com dois manuais, o mais recente é o que vale (lib/voz.js, textoDaMarca);
            os outros dizem-no, em vez de se ficar sem saber qual é o actual. */}
        {total === 0 ? (
          <div className="mt-3 border border-dashed border-border bg-card px-6 py-10 text-center">
            <p className="text-sm text-foreground">Ainda não há nada aqui.</p>
            <p className="mx-auto mt-2 max-w-prose text-xs leading-relaxed text-muted-foreground">
              {/* O perfil Leitura era convidado a carregar o que não pode (agentes, 20 Out). */}
              {podeMexer
                ? 'Larga o manual de normas, o logótipo e umas fotografias, todos de uma vez. Ou acrescenta o site da marca.'
                : 'Quem carrega os ficheiros da marca é o Responsável da conta ou um Editor.'}
            </p>
          </div>
        ) : (
          <ul className={filtro === 'imagem'
            ? 'mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3'
            : 'mt-3 border border-border bg-card'}>
            {materiais.map((m) => (
              <LinhaFicheiro
                key={m.id}
                emGrelha={filtro === 'imagem'}
                material={m}
                fonte={fontePorMaterial[m.id] || null}
                novo={novos.has(m.id)}
                manualAntigo={m.papel === 'manual' && manualActual && m.id !== manualActual.id}
                parado={parado}
                podeMexer={podeMexer}
                ocupado={ocupado}
                accoes={accoes}
              />
            ))}
            {sitesVisiveis.map((f) => (
              <KbFonte
                key={f.id}
                fonte={f}
                etapas={null}
                parado={parado}
                podeMexer={podeMexer}
                ocupado={ocupado}
                onReler={accoes.reler}
                onRetomar={accoes.retomar}
                onAprovar={accoes.aprovar}
                onApagar={accoes.removerSite}
              />
            ))}
          </ul>
        )}
      </section>

      {podeMexer && sites.some((f) => f.status === 'done') && (
        <div className="flex flex-wrap items-center gap-3 text-xs">
          <span className="text-muted-foreground">Actualizar a referência visual de um site já lido:</span>
          {sites.filter((f) => f.status === 'done').map((f) => <button key={f.id} type="button" disabled={aRecolherSite || ocupado} onClick={() => recolherSite(f)} className="border border-border px-3 py-2 disabled:opacity-50">Recolher identidade de {f.title || f.source_url || 'site'}</button>)}
        </div>
      )}

      <ConfirmarApagarFicheiro
        material={aApagar}
        fonte={aApagar ? fontePorMaterial[aApagar.id] || null : null}
        ocupado={ocupado}
        onFechar={() => setAApagar(null)}
        onConfirmar={apagar}
        onDeixarDeLer={accoes.deixarDeLer}
      />
      <Toast toast={toast} onFechar={fecharToast} />
    </div>
  );
}
