/**
 * Base de conhecimento — o que o cliente nos dá para lermos.
 *
 * A API existia desde 22 Set e **nada lhe chamava**. Este é o ecrã que faz
 * dela uma funcionalidade em vez de um conjunto de endpoints.
 *
 * ## O que este ecrã tem de explicar bem
 *
 * Uma fonte passa por seis estados e três deles pedem alguma coisa a alguém.
 * Um ponto colorido não chega: quem está aqui quer saber **se tem de fazer
 * alguma coisa**, e a resposta tem de estar escrita.
 *
 * - `blocked` é o mais importante, e é o único que o cliente pode resolver
 *   sozinho. A razão que vem do servidor diz-lhe o quê — "pede para
 *   autorizarem o nosso crawler". Esconder isso atrás de "falhou" deitava
 *   fora o trabalho todo de distinguir um site que barra de um crawl partido.
 * - `review_pending` é uma pessoa a ter de olhar, e tem de dizer **porquê**:
 *   uma captura de um post traz os comentários dos consumidores com ela, e
 *   isso não entra na base de conhecimento sem alguém ver.
 * - `error` é nosso, e o que se oferece é tentar outra vez.
 *
 * ## A declaração de autorização não é uma caixinha
 *
 * Fica registada na auditoria com o email de quem a ticou. É por isso que o
 * texto diz o que diz e não um "aceito os termos": o que está a ser declarado
 * é que aquele site é da marca dele ou que ele tem autorização para nós o
 * lermos. Um texto vago tornava o registo inútil.
 */

const KB_ESTADOS = {
  pending: {
    texto: 'Na fila',
    detalhe: 'Vai ser lida dentro de minutos.',
  },
  processing: {
    texto: 'A ler',
    detalhe: 'Está a ser lida agora.',
  },
  review_pending: {
    texto: 'À espera de revisão',
    detalhe:
      'O texto saiu de uma imagem e alguém tem de o ver antes de entrar. '
      + 'Uma captura de um post costuma trazer comentários de clientes com ela, '
      + 'e isso não é material de marca.',
    atencao: true,
  },
  done: { texto: 'Lida', detalhe: null },
  error: {
    texto: 'Falhou',
    detalhe: 'Não conseguimos ler isto.',
    atencao: true,
  },
  blocked: {
    texto: 'Bloqueada',
    detalhe: 'O site não nos deixou entrar.',
    atencao: true,
  },
};

const KB_TIPOS = {
  crawl: 'Site',
  social: 'Rede social',
  upload: 'Ficheiro',
  ocr: 'Imagem',
  manual: 'Escrito à mão',
};

function kbTamanho(bytes) {
  if (!bytes) return null;
  if (bytes < 1024 * 1024) return `${Math.max(1, Math.round(bytes / 1024))} KB`;
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}

/**
 * Quanto espaço resta.
 *
 * Aparece por cima do formulário e não escondido num canto: o objectivo é
 * ninguém arrastar um ficheiro de 200 MB para ouvir no fim que não cabia.
 */
function KbEspaco({ espaco }) {
  if (!espaco || espaco.limite === null) return null;

  const fraccao = Math.min(1, espaco.usado / espaco.limite);
  const cheio = fraccao >= 0.9;

  return (
    <div>
      <div className="flex items-baseline justify-between gap-3">
        <p className="label-eyebrow">Espaço</p>
        <p className={`text-xs ${cheio ? 'text-foreground' : 'text-muted-foreground'}`}>
          {(espaco.usado / 1e9).toFixed(1)} de {(espaco.limite / 1e9).toFixed(0)} GB
        </p>
      </div>
      <div className="mt-1.5 h-1 w-full bg-secondary">
        <div
          className={`h-1 ${cheio ? 'bg-primary' : 'bg-muted-foreground'}`}
          style={{ width: `${Math.max(2, fraccao * 100)}%` }}
        />
      </div>
    </div>
  );
}

/** As etapas de uma fonte — de "A minha marca" (lib/marca.js no servidor). */
const COR_ETAPA = {
  feito: '--online', em_curso: '--accent', a_espera: '--accent', falhou: '--offline', por_fazer: '--unknown',
};
// Quem tem a etapa nas mãos (o percurso de cada fonte, no back office).
const DONO_ETAPA = { sistema: 'Com o sistema', netscreen: 'Com a NetScreen', cliente: 'Contigo' };

function KbEtapas({ etapas }) {
  return (
    <ol className="mt-3 flex flex-wrap gap-x-5 gap-y-2" aria-label="Percurso desta fonte">
      {etapas.map((e) => (
        <li key={e.chave} className="flex min-w-0 items-start gap-1.5 text-xs">
          <span className="mt-1 h-2 w-2 shrink-0" style={{ background: `hsl(var(${COR_ETAPA[e.estado]}))` }} aria-hidden="true" />
          <span className="min-w-0">
            <span className={e.estado === 'por_fazer' ? 'text-muted-foreground' : 'text-foreground'}>{e.rotulo}</span>
            {e.detalhe && <span className="text-muted-foreground"> · {e.detalhe}</span>}
            {e.dono && e.estado !== 'feito' && (
              <span className="block text-muted-foreground">{DONO_ETAPA[e.dono]}</span>
            )}
            {e.erros && e.erros.length > 0 && (
              <span className="block text-muted-foreground">
                {e.erros.map((p) => `${p.pagina != null ? `pág. ${p.pagina}` : (p.url || 'página')}: ${p.frase}`).join(' · ')}
              </span>
            )}
          </span>
        </li>
      ))}
    </ol>
  );
}

/** Uma fonte na lista. */
function KbFonte({ fonte, etapas, podeMexer, ocupado, parado, onReler, onRetomar, onAprovar, onApagar }) {
  const [aConfirmar, setAConfirmar] = React.useState(false);
  const base = KB_ESTADOS[fonte.status] || { texto: fonte.status, detalhe: null };
  // Com o processamento parado, "vai ser lida dentro de minutos" é mentira.
  const estado = parado && (fonte.status === 'pending' || fonte.status === 'processing')
    ? { ...base, detalhe: 'O processamento está parado. Vai ser lida assim que voltar.', atencao: true }
    : base;

  // A razão que vem do servidor ganha sempre à frase genérica: foi escrita
  // para este caso e diz o que fazer.
  const explicacao = fonte.error_message || estado.detalhe;

  return (
    <li className="border-t border-border px-5 py-4 first:border-t-0">
      <div className="flex flex-wrap items-baseline justify-between gap-3">
        <div className="min-w-0">
          <p className="truncate text-sm text-foreground">{fonte.label}</p>
          <p className="mt-0.5 text-xs text-muted-foreground">
            {KB_TIPOS[fonte.type] || fonte.type}
            {fonte.tamanho ? ` · ${kbTamanho(fonte.tamanho)}` : ''}
            {fonte.status === 'done' && fonte.paginas_ok > 0
              ? ` · ${fonte.paginas_ok} ${fonte.paginas_ok === 1 ? 'página' : 'páginas'}`
              : ''}
            {fonte.created_at ? ` · ${formatDataCurta(fonte.created_at)}` : ''}
          </p>
        </div>

        <span
          role={['pending', 'processing'].includes(fonte.status) ? 'status' : undefined}
          className={`inline-flex shrink-0 items-center gap-1.5 text-xs uppercase tracking-wider ${
            estado.atencao ? 'text-primary' : 'text-muted-foreground'
          }`}
        >
          {/* Na fila e a ler, gira (8 Out): sem isso parecia parado. */}
          {['pending', 'processing'].includes(fonte.status) && <ATrabalhar className="text-primary" />}
          {estado.texto}
        </span>
      </div>

      {explicacao && (
        <p className="mt-2 max-w-prose text-xs leading-relaxed text-muted-foreground">{explicacao}</p>
      )}

      {etapas && <KbEtapas etapas={etapas} />}

      {podeMexer && (
        <div className="mt-3 flex flex-wrap gap-2">
          {fonte.status === 'review_pending' && (
            <button
              type="button"
              disabled={ocupado}
              onClick={() => onAprovar(fonte)}
              className="bg-primary px-3 py-1.5 text-xs 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"
            >
              Aprovar
            </button>
          )}
          {/* Uma leitura a meio continua-se pelo mesmo trabalho do Firecrawl,
              sem pagar outra vez as páginas já lidas (api/kb.js, /retomar).
              É o servidor que diz se ainda dá (pode_retomar). */}
          {fonte.pode_retomar && (
            <button
              type="button"
              disabled={ocupado}
              onClick={() => onRetomar(fonte)}
              className="bg-primary px-3 py-1.5 text-xs 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"
            >
              Continuar a leitura
            </button>
          )}
          {/* Só se relê o que tem de onde ser relido -- a mesma regra do
              servidor (api/kb.js, podeSerRelida): um site pelo endereço, um
              ficheiro pelo ficheiro. Uma fonte escrita à mão, ou uma captura
              registada sem ficheiro, não mostram o botão. */}
          {['done', 'error', 'blocked'].includes(fonte.status)
            && ((['crawl', 'social'].includes(fonte.type) && fonte.source_url)
              || (['upload', 'ocr'].includes(fonte.type) && fonte.tem_ficheiro)) && (
            <button
              type="button"
              disabled={ocupado}
              onClick={() => onReler(fonte)}
              className="border border-border px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:border-primary hover:text-foreground disabled:opacity-50"
            >
              Ler outra vez
            </button>
          )}
          {/* Confirmar no próprio botão (2.º ensaio NESCAFÉ, 30 Out: um clique tirava o site sem perguntar). */}
          {aConfirmar ? (
            <>
              <button
                type="button"
                disabled={ocupado}
                onClick={() => { setAConfirmar(false); onApagar(fonte); }}
                className="border border-destructive px-3 py-1.5 text-xs text-foreground transition-colors disabled:opacity-50"
              >
                Confirmar remoção
              </button>
              <button
                type="button"
                onClick={() => setAConfirmar(false)}
                className="border border-border px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:border-primary hover:text-foreground"
              >
                Cancelar
              </button>
            </>
          ) : (
            <button
              type="button"
              disabled={ocupado}
              onClick={() => setAConfirmar(true)}
              className="border border-border px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:border-primary hover:text-foreground disabled:opacity-50"
            >
              Remover
            </button>
          )}
        </div>
      )}
    </li>
  );
}

/**
 * Acrescentar um site.
 *
 * O botão só acorda com a declaração ticada, e é de propósito: a alternativa
 * era deixar submeter e dar erro, o que ensina menos e irrita mais.
 */
function KbFormSite({ ocupado, onGuardar }) {
  const [url, setUrl] = React.useState('');
  const [autorizo, setAutorizo] = React.useState(false);

  const submeter = (e) => {
    e.preventDefault();
    if (!url.trim() || !autorizo) return;
    onGuardar({ type: 'crawl', source_url: url.trim(), autorizo: true }, () => {
      setUrl('');
      setAutorizo(false);
    });
  };

  return (
    <form onSubmit={submeter} className="space-y-3">
      <div>
        <label htmlFor="kb-url" className="label-eyebrow">
          Endereço
        </label>
        <input
          id="kb-url"
          type="url"
          value={url}
          onChange={(e) => setUrl(e.target.value)}
          placeholder="https://aminhamarca.pt"
          className="mt-1 w-full border border-border bg-background px-3 py-2 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-primary"
        />
        <p className="mt-1.5 text-xs text-muted-foreground">
          Uma página geral da marca. Páginas de produtos, serviços ou promoções podem entrar como referências de uma campanha.
        </p>
      </div>

      <label htmlFor="kb-autorizo" className="flex cursor-pointer items-start gap-2.5">
        <input
          id="kb-autorizo"
          type="checkbox"
          checked={autorizo}
          onChange={(e) => setAutorizo(e.target.checked)}
          className="mt-0.5 shrink-0 accent-primary"
        />
        <span className="text-xs leading-relaxed text-muted-foreground">
          Confirmo que este site é da minha marca, ou que tenho autorização para o lermos.
          Fica registado quem fez esta declaração.
        </span>
      </label>

      <button
        type="submit"
        disabled={ocupado || !url.trim() || !autorizo}
        className="bg-primary px-4 py-2 text-xs uppercase tracking-wider text-primary-foreground outline-none hover:bg-primary/90 focus-visible:ring-1 focus-visible:ring-accent disabled:cursor-not-allowed disabled:bg-secondary disabled:text-muted-foreground"
      >
        Acrescentar
      </button>
    </form>
  );
}

/**
 * Carregar um ficheiro.
 *
 * O corpo do pedido é o ficheiro em bruto — é o que a API espera, e do lado
 * do browser é menos código do que montar um `FormData`.
 */
function KbFormFicheiro({ slug, ocupado, onFeito, onErro }) {
  const campo = React.useRef(null);
  const [aEnviar, setAEnviar] = React.useState(null);
  // Uma fotografia não tem nada a ensinar ao agente, e lê-la por OCR punha-a
  // "à espera de revisão" contigo -- quatro aprovações sem valor numa marca
  // nova (avaliação de workload, 23 Set). Só se lê se disseres que tem texto.
  const [lerImagem, setLerImagem] = React.useState(false);

  const enviar = async (ficheiro) => {
    if (!ficheiro) return;
    const imagem = /^image\/(png|jpeg|webp)$/.test(ficheiro.type) || /\.(png|jpe?g|webp)$/i.test(ficheiro.name);
    const semLer = imagem && !lerImagem;
    setAEnviar(ficheiro.name);
    try {
      const r = await fetch(`/api/${slug}/materiais?nome=${encodeURIComponent(ficheiro.name)}${semLer ? '&ler=0' : ''}`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': ficheiro.type || 'application/octet-stream' },
        body: ficheiro,
      });
      const corpo = await r.json().catch(() => null);
      if (!r.ok) throw new Error((corpo && corpo.error) || `Erro ${r.status}`);
      onFeito(semLer
        ? `${ficheiro.name} guardado na Biblioteca, pronto para usar nas campanhas.`
        : `${ficheiro.name} carregado.`);
    } catch (e) {
      onErro(e.message);
    } finally {
      setAEnviar(null);
      if (campo.current) campo.current.value = '';
    }
  };

  return (
    <div className="space-y-3">
      <div>
        <p className="label-eyebrow mb-1">Ficheiro</p>
        <ZonaFicheiro
          id="kb-ficheiro"
          campoRef={campo}
          desactivado={ocupado || aEnviar !== null}
          aEnviar={aEnviar}
          onFicheiro={enviar}
        />
      </div>

      <p className="max-w-prose text-xs leading-relaxed text-muted-foreground">
        Manuais de normas, catálogos, apresentações, folhas de preços — PDF, Word, PowerPoint,
        Excel, CSV ou texto. Fotografias e logótipos em PNG, JPG ou WebP vão para a Biblioteca,
        prontos para as campanhas, sem nada para aprovar.
      </p>

      <label htmlFor="kb-ler-imagem" className="flex cursor-pointer items-start gap-2.5">
        <input
          id="kb-ler-imagem"
          type="checkbox"
          checked={lerImagem}
          onChange={(e) => setLerImagem(e.target.checked)}
          className="mt-0.5 accent-[hsl(var(--primary))]"
        />
        <span className="text-xs leading-relaxed text-muted-foreground">
          A imagem tem texto que o agente deve ler (um cartaz, uma captura de um post). O texto é
          lido, e depois confirmas tu, porque uma captura traz os comentários de outras pessoas.
        </span>
      </label>

    </div>
  );
}

function ScreenConfigKb({ slug, podeMexer = true, embutido = false, etapasPorFonte = null, onMudou = null }) {
  const [fontes, setFontes] = React.useState(null);
  const [espaco, setEspaco] = React.useState(null);
  const [processamento, setProcessamento] = React.useState(null);
  const [erro, setErro] = React.useState(null);
  const [ocupado, setOcupado] = React.useState(false);
  const [modo, setModo] = React.useState('site');
  const [toast, setToast] = React.useState(null);
  const fecharToast = React.useCallback(() => setToast(null), []);

  const carregar = React.useCallback(
    () =>
      Promise.all([
        api.get(`/api/${slug}/kb/fontes`),
        api.get(`/api/${slug}/materiais/espaco`),
        api.get(`/api/${slug}/kb/processamento`).catch(() => null),
      ])
        .then(([f, e, p]) => {
          setFontes(f);
          setEspaco(e);
          setProcessamento(p);
          // Embutido em "A minha marca", o percurso e o topo vêm de outra rota:
          // cada recarga daqui (uma fonte nova, reler, aprovar, a sondagem)
          // tem de os refazer, senão uma fonte nova ficava sem etapas.
          if (onMudou) onMudou();
        })
        .catch((e) => setErro(e.message || 'Não foi possível ler as fontes.')),
    [slug, onMudou]
  );

  React.useEffect(() => {
    setFontes(null);
    setErro(null);
    carregar();
  }, [carregar]);

  // Enquanto houver fontes a meio, volta a perguntar. Sem isto, quem acabou
  // de acrescentar um site ficava a olhar para "Na fila" até recarregar a
  // página à mão, sem saber se aquilo estava vivo.
  const aTrabalhar = (fontes || []).some((f) => f.status === 'pending' || f.status === 'processing');
  React.useEffect(() => {
    if (!aTrabalhar) return undefined;
    const t = setInterval(carregar, 8000);
    return () => clearInterval(t);
  }, [aTrabalhar, 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 guardarSite = (corpo, limpar) =>
    comAviso(async () => {
      await api.post(`/api/${slug}/kb/fontes`, corpo);
      limpar();
    }, 'Site acrescentado. Vai ser lido dentro de minutos.');

  const cabecalho = (
    <header>
      <h1 className="mt-1 font-display text-4xl uppercase leading-none tracking-wide">
        Base de conhecimento
      </h1>
      <p className="mt-3 max-w-prose text-sm leading-relaxed text-muted-foreground">
        O que nos deres aqui é o que o agente lê antes de escrever seja o que for para a tua marca.
        Quanto melhor for este material, menos ele inventa.
      </p>
    </header>
  );

  if (erro) {
    return (
      <div className="space-y-6">
        {!embutido && cabecalho}
        <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 as fontes</p>
          <p className="mt-2 text-sm text-muted-foreground">{erro}</p>
        </div>
      </div>
    );
  }

  if (!fontes) {
    return (
      <div className="space-y-6">
        {!embutido && cabecalho}
        <SkeletonTabela linhas={3} />
      </div>
    );
  }

  return (
    <div className="space-y-6">
      {!embutido && cabecalho}

      {podeMexer && (
        <section className="border border-border bg-card p-5">
          <div className="flex flex-wrap items-center justify-between gap-4">
            <div className="flex gap-2">
              {[
                ['site', 'Um site'],
                ['ficheiro', 'Um ficheiro'],
              ].map(([chave, texto]) => (
                <button
                  key={chave}
                  type="button"
                  onClick={() => setModo(chave)}
                  className={`px-3 py-1.5 text-xs uppercase tracking-wider transition-colors ${
                    modo === chave
                      ? 'bg-secondary text-foreground'
                      : 'text-muted-foreground hover:text-foreground'
                  }`}
                >
                  {texto}
                </button>
              ))}
            </div>
            <div className="min-w-[10rem] grow sm:max-w-xs">
              <KbEspaco espaco={espaco} />
            </div>
          </div>

          <div className="mt-4">
            {modo === 'site' ? (
              <KbFormSite ocupado={ocupado} onGuardar={guardarSite} />
            ) : (
              <KbFormFicheiro
                slug={slug}
                ocupado={ocupado}
                onFeito={(m) => {
                  setToast({ id: Date.now(), mensagem: m });
                  carregar();
                }}
                onErro={(m) => setToast({ id: Date.now(), mensagem: m })}
              />
            )}
          </div>
        </section>
      )}

      <section>
        <h2 className="label-eyebrow">O que já temos</h2>

        {/* A 23 Set um PDF ficou "dentro de minutos" o dia inteiro porque o
            worker não estava a correr, e ninguém soube. */}
        {processamento && processamento.parado && (
          <div className="mt-3 border-l-2 border-accent bg-[hsl(var(--accent)/0.08)] px-4 py-3" role="status">
            <p className="text-sm text-foreground">
              O processamento está parado: {processamento.a_espera === 1 ? 'uma fonte está' : `${processamento.a_espera} fontes estão`} à espera e não estão a ser lidas agora.
            </p>
            <p className="mt-1 text-xs text-muted-foreground">
              A equipa NetScreen vê este alerta no back office. Não precisas de voltar a carregar nada: o que está na
              fila é lido assim que o processamento voltar.
            </p>
          </div>
        )}

        {fontes.length === 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">
              Começa pelo site da marca. Depois acrescenta o manual de normas, o catálogo, ou
              aquela apresentação que toda a gente usa e ninguém sabe onde está.
            </p>
          </div>
        ) : (
          <ul className="mt-3 border border-border bg-card">
            {fontes.map((f) => (
              <KbFonte
                key={f.id}
                fonte={f}
                etapas={etapasPorFonte ? etapasPorFonte[f.id] : null}
                parado={Boolean(processamento && processamento.parado)}
                podeMexer={podeMexer}
                ocupado={ocupado}
                onReler={(fonte) =>
                  comAviso(
                    () => api.post(`/api/${slug}/kb/fontes/${fonte.id}/reler`, {}),
                    'Vai ser lida outra vez.'
                  )
                }
                onRetomar={(fonte) =>
                  comAviso(
                    () => api.post(`/api/${slug}/kb/fontes/${fonte.id}/retomar`, {}),
                    'Vai continuar de onde ficou.'
                  )
                }
                onAprovar={(fonte) =>
                  comAviso(
                    () => api.post(`/api/${slug}/kb/fontes/${fonte.id}/aprovar`, {}),
                    'Aprovada.'
                  )
                }
                onApagar={(fonte) =>
                  comAviso(() => api.del(`/api/${slug}/kb/fontes/${fonte.id}`), 'Removida.')
                }
              />
            ))}
          </ul>
        )}
      </section>

      <Toast toast={toast} onFechar={fecharToast} />
    </div>
  );
}
