/**
 * <Tabela> — listagem com ordenação, pesquisa e estados.
 *
 * É o primitivo de que todos os ecrãs do back office precisam: lista de
 * clientes, hardware, utilizadores, fontes de conhecimento. Por isso trata
 * aqui, uma vez, do que costuma ficar por fazer em cada ecrã:
 *
 *   - **Vazio e sem resultados são coisas diferentes.** "Ainda não há clientes"
 *     pede um botão para criar; "a pesquisa não deu nada" pede um botão para
 *     limpar a pesquisa. Trocar os dois deixa o utilizador encalhado
 *   - **No telemóvel não há tabela.** Abaixo de md cada linha vira um cartão
 *     com rótulo e valor, porque uma tabela de oito colunas a 390 px não se lê.
 *     A coluna marcada `principal` é o título do cartão
 *   - **Ordenar é por botão**, com `aria-sort`, e não por um `<th>` clicável
 *     que o teclado não alcança
 *   - **Os números alinham à direita** e usam `tabular-nums`, senão uma coluna
 *     de euros fica em ziguezague
 *
 * Sem paginação: as listagens do back office são de dezenas de linhas e a
 * maior, o hardware, anda pelas centenas. Quando alguma passar disso, é aqui
 * que se acrescenta.
 *
 * Colunas:
 *   { chave, rotulo, ordenavel, principal, alinhamento: 'direita',
 *     rotuloCartao: o rótulo nos cartões móveis, quando o da tabela não serve (ex.: uma checkbox
 *     «seleccionar todos» que se repetia em cada cartão, 8 Out),
 *     render: (linha) => JSX, valorOrdenacao: (linha) => any, largura: 'w-32' }
 */

function ordenar(linhas, coluna, sentido) {
  if (!coluna) return linhas;
  const valor = (l) => (coluna.valorOrdenacao ? coluna.valorOrdenacao(l) : l[coluna.chave]);
  const sinal = sentido === 'desc' ? -1 : 1;

  return [...linhas].sort((a, b) => {
    const va = valor(a);
    const vb = valor(b);
    // Vazios sempre no fim, independentemente do sentido: uma linha sem data
    // não é "a mais antiga", é uma linha sem data.
    if (va == null && vb == null) return 0;
    if (va == null) return 1;
    if (vb == null) return -1;
    if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * sinal;
    return String(va).localeCompare(String(vb), 'pt', { numeric: true }) * sinal;
  });
}

function TabelaOrdenacao({ coluna, ordem, aoOrdenar }) {
  const activa = ordem.chave === coluna.chave;
  const sentido = activa ? ordem.sentido : null;

  return (
    <button
      type="button"
      onClick={() => aoOrdenar(coluna.chave)}
      className="group inline-flex items-center gap-1.5 whitespace-nowrap text-left outline-none focus-visible:ring-1 focus-visible:ring-accent"
    >
      {coluna.rotulo}
      <span className={activa ? 'text-accent' : 'text-transparent group-hover:text-muted-foreground'}>
        {sentido === 'desc' ? '↓' : '↑'}
      </span>
    </button>
  );
}

function Tabela({
  colunas,
  linhas,
  chaveLinha = (l) => l.id,
  aoClicar,
  pesquisa,
  ordemInicial,
  vazio,
  carregando = false,
  contagem = true,
}) {
  const [termo, setTermo] = React.useState('');
  const [ordem, setOrdem] = React.useState(ordemInicial || { chave: null, sentido: 'asc' });

  const principal = colunas.find((c) => c.principal) || colunas[0];

  function alternarOrdem(chave) {
    setOrdem((o) =>
      o.chave === chave
        ? { chave, sentido: o.sentido === 'asc' ? 'desc' : 'asc' }
        : { chave, sentido: 'asc' }
    );
  }

  const filtradas = React.useMemo(() => {
    if (!pesquisa || !termo.trim()) return linhas;
    const t = termo.trim().toLowerCase();
    const campos = pesquisa.campos || colunas.map((c) => c.chave);
    return linhas.filter((l) =>
      campos.some((c) => String(l[c] ?? '').toLowerCase().includes(t))
    );
  }, [linhas, termo, pesquisa, colunas]);

  const visiveis = React.useMemo(() => {
    const col = colunas.find((c) => c.chave === ordem.chave);
    return ordenar(filtradas, col, ordem.sentido);
  }, [filtradas, colunas, ordem]);

  const celula = (coluna, linha) =>
    coluna.render ? coluna.render(linha) : linha[coluna.chave];

  const campoPesquisa = pesquisa && (
    <div className="flex min-w-0 flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3">
      {/* `w-full` até sm e largura fixa a partir daí. Um `max-w` em píxeis aqui
          empurrava a linha para lá da largura do ecrã a 400 px. */}
      <input
        type="search"
        value={termo}
        onChange={(e) => setTermo(e.target.value)}
        placeholder={pesquisa.placeholder || 'Pesquisar'}
        aria-label={pesquisa.placeholder || 'Pesquisar'}
        className="w-full min-w-0 border border-border bg-background px-3 py-1.5 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-accent sm:w-64"
      />
      {/* Em carregamento não se anuncia contagem nenhuma: "0 registos" antes de
          os dados chegarem lê-se como "não há nada", que é outra coisa. */}
      {contagem && !carregando && (
        <span className="text-xs uppercase tracking-wider text-muted-foreground">
          {visiveis.length === linhas.length
            ? `${linhas.length} ${linhas.length === 1 ? 'registo' : 'registos'}`
            : `${visiveis.length} de ${linhas.length}`}
        </span>
      )}
    </div>
  );

  if (carregando) {
    return (
      <div className="overflow-hidden border border-border bg-card">
        {campoPesquisa}
        <SkeletonTabela linhas={5} />
      </div>
    );
  }

  // Sem dados nenhuns e sem pesquisa activa: é o estado vazio de verdade.
  if (!linhas.length) {
    return (
      <div className="border border-border bg-card px-4 py-14 text-center sm:px-6">
        <p className="font-display text-xl tracking-wide text-foreground">
          {(vazio && vazio.titulo) || 'Sem registos'}
        </p>
        {vazio && vazio.descricao && (
          <p className="mx-auto mt-2 max-w-sm text-sm text-muted-foreground">{vazio.descricao}</p>
        )}
        {vazio && vazio.accao && <div className="mt-5">{vazio.accao}</div>}
      </div>
    );
  }

  return (
    <div className="overflow-hidden border border-border bg-card">
      {campoPesquisa}

      {/* A pesquisa não deu nada — diferente de não haver dados. */}
      {!visiveis.length ? (
        <div className="px-6 py-12 text-center">
          <p className="text-sm text-foreground">Nada corresponde a "{termo}".</p>
          <button
            type="button"
            onClick={() => setTermo('')}
            className="mt-3 border border-border px-3 py-1.5 text-xs uppercase tracking-wider text-muted-foreground outline-none hover:border-primary hover:text-primary focus-visible:ring-1 focus-visible:ring-accent"
          >
            Limpar pesquisa
          </button>
        </div>
      ) : (
        <React.Fragment>
          {/* Tabela, a partir de md */}
          <div className="hidden overflow-x-auto md:block">
            <table className="w-full border-collapse text-sm">
              <thead>
                <tr className="border-b border-border">
                  {colunas.map((c) => (
                    <th
                      key={c.chave}
                      scope="col"
                      aria-sort={
                        ordem.chave === c.chave
                          ? ordem.sentido === 'asc'
                            ? 'ascending'
                            : 'descending'
                          : c.ordenavel
                          ? 'none'
                          : undefined
                      }
                      className={`whitespace-nowrap px-3 py-2.5 text-xs font-medium text-muted-foreground ${
                        c.alinhamento === 'direita' ? 'text-right' : 'text-left'
                      } ${c.largura || ''}`}
                    >
                      {c.ordenavel ? (
                        <TabelaOrdenacao coluna={c} ordem={ordem} aoOrdenar={alternarOrdem} />
                      ) : (
                        c.rotulo
                      )}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {visiveis.map((linha) => (
                  <tr
                    key={chaveLinha(linha)}
                    onClick={aoClicar ? () => aoClicar(linha) : undefined}
                    className={`border-b border-border last:border-b-0 ${
                      aoClicar ? 'cursor-pointer hover:bg-secondary' : ''
                    }`}
                  >
                    {colunas.map((c) => (
                      <td
                        key={c.chave}
                        className={`px-3 py-3 align-middle ${c === principal ? 'min-w-[11rem]' : ''} ${
                          c.alinhamento === 'direita' ? 'text-right tabular-nums' : 'text-left'
                        } ${c === principal ? 'text-foreground' : 'text-muted-foreground'}`}
                      >
                        {/* O alvo de teclado é o botão da coluna principal, não
                            a linha: assim a tabela continua a ler-se como tabela. */}
                        {c === principal && aoClicar ? (
                          <button
                            type="button"
                            onClick={(e) => {
                              e.stopPropagation();
                              aoClicar(linha);
                            }}
                            className="text-left font-medium text-foreground outline-none hover:text-primary focus-visible:ring-1 focus-visible:ring-accent"
                          >
                            {celula(c, linha)}
                          </button>
                        ) : (
                          celula(c, linha)
                        )}
                      </td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>

          {/* Cartões, abaixo de md */}
          <div className="divide-y divide-border md:hidden">
            {visiveis.map((linha) => (
              <div key={chaveLinha(linha)} className="px-4 py-3.5">
                <div className="font-medium text-foreground">
                  {aoClicar ? (
                    <button
                      type="button"
                      onClick={() => aoClicar(linha)}
                      className="text-left outline-none hover:text-primary focus-visible:ring-1 focus-visible:ring-accent"
                    >
                      {celula(principal, linha)}
                    </button>
                  ) : (
                    celula(principal, linha)
                  )}
                </div>
                {/* `minmax(0,auto)` para um rótulo comprido não alargar a coluna
                    para lá do ecrã; o valor fica com o resto e parte linha. */}
                <dl className="mt-2 grid grid-cols-[minmax(0,auto),1fr] gap-x-4 gap-y-1.5 text-sm">
                  {colunas
                    .filter((c) => c !== principal)
                    .map((c) => (
                      <React.Fragment key={c.chave}>
                        <dt className="text-xs uppercase tracking-wider text-muted-foreground">
                          {c.rotuloCartao ?? c.rotulo}
                        </dt>
                        <dd className="text-foreground">{celula(c, linha)}</dd>
                      </React.Fragment>
                    ))}
                </dl>
              </div>
            ))}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}
