/* global React, Icon, Button, Drawer, FooterPager, fmtDatetime */
// ============================================================
// Componentes_Lista.jsx — os componentes de LISTA do redesenho do Marketplace (Lote A, 10/10/2026)
//
//   TabelaDados      — tabela com ordenação, cabeçalho fixo, 25/50/100 por página (na memória OU no servidor, com a
//                      contagem de lá), seleção + barra de ações em lote, mostrar/ocultar colunas, e Enter abre a linha
//   PainelRegistro   — o painel lateral de um registro, com as AÇÕES dentro (o Drawer, que cuida do foco)
//   EstadoDados      — carregando · vazio · erro · desatualizado · sucesso, com texto (nunca só cor)
//   useConsultaTabela — página, linhas por página e ordem de uma tabela, guardadas na aba do navegador
//
// Os nomes são genéricos de propósito: nasceram para o Marketplace e servem a qualquer módulo. Tudo o que decide
// (ordenar, paginar, selecionar, guardar) mora em funções puras `td*`, que a PROVA_componentes-lista.js roda.
// ============================================================

const TD_POR_PAGINA = [25, 50, 100];
const TD_CHAVE_ESTADO = 'tm-td:';      // sessionStorage: página, linhas por página e ordem (voltam ao voltar à lista)
const TD_CHAVE_COLUNAS = 'tm-td-col:'; // localStorage: as colunas ocultas (são preferência, não estado)

// ── funções puras ───────────────────────────────────────────────────────────────────────────────────────────
// Ordena sem mexer na lista recebida. Vazio (null, undefined, '') vai SEMPRE para o fim, nos dois sentidos — "sem
// dado" não é o menor nem o maior valor. Número com número, data ISO com data, texto pelo português (sem acento).
function tdComparar(a, b) {
  const va = a === '' ? null : a, vb = b === '' ? null : b;
  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;
  if (typeof va === 'boolean' || typeof vb === 'boolean') return (va ? 1 : 0) - (vb ? 1 : 0);
  return String(va).localeCompare(String(vb), 'pt-BR', { sensitivity: 'base', numeric: true });
}
function tdOrdenar(linhas, coluna, dir) {
  const lista = Array.isArray(linhas) ? linhas.map((l, i) => ({ l, i })) : [];
  if (!coluna || typeof coluna.valor !== 'function') return lista.map((x) => x.l);
  const sinal = dir === 'desc' ? -1 : 1;
  lista.sort((x, y) => {
    const a = coluna.valor(x.l), b = coluna.valor(y.l);
    const vazioA = a == null || a === '', vazioB = b == null || b === '';
    if (vazioA !== vazioB) return vazioA ? 1 : -1;            // vazio no fim, mesmo em ordem decrescente
    return (sinal * tdComparar(a, b)) || (x.i - y.i);          // empate: a ordem que veio (ordenação estável)
  });
  return lista.map((x) => x.l);
}
// A página pedida se ajusta ao que existe: filtrar até sobrarem poucas linhas não pode deixar a pessoa numa página
// vazia (a regra do FooterPager, CLAUDE.md § 7).
function tdPaginar(total, porPagina, pagina) {
  const pp = TD_POR_PAGINA.includes(Number(porPagina)) ? Number(porPagina) : TD_POR_PAGINA[0];
  const n = Math.max(0, Number(total) || 0);
  const totalPaginas = Math.max(1, Math.ceil(n / pp));
  const atual = Math.min(Math.max(Number(pagina) || 1, 1), totalPaginas);
  return { porPagina: pp, totalPaginas, atual, inicio: (atual - 1) * pp, fim: Math.min(atual * pp, n), total: n };
}
// A próxima ordem ao clicar no cabeçalho: crescente → decrescente → crescente (coluna nova começa crescente).
function tdProximaOrdem(ordem, colId) {
  if (!ordem || ordem.col !== colId) return { col: colId, dir: 'asc' };
  return { col: colId, dir: ordem.dir === 'asc' ? 'desc' : 'asc' };
}
// Seleção: um conjunto de CHAVES. Marcar a página marca as chaves dela; desmarcar tira só as dela.
function tdAlternar(sel, chave) { const n = new Set(sel); if (n.has(chave)) n.delete(chave); else n.add(chave); return n; }
function tdMarcarPagina(sel, chavesDaPagina, marcar) {
  const n = new Set(sel);
  for (const k of chavesDaPagina) { if (marcar) n.add(k); else n.delete(k); }
  return n;
}
function tdEstadoDaPagina(sel, chavesDaPagina) {
  const marcadas = chavesDaPagina.filter((k) => sel.has(k)).length;
  return marcadas === 0 ? 'nenhuma' : (marcadas === chavesDaPagina.length ? 'todas' : 'algumas');
}
// O estado guardado: lido com try/catch (aba anônima, armazenamento bloqueado), validado, com padrão silencioso.
function tdLerEstado(id, armazem) {
  const vazio = { pagina: 1, porPagina: null, ordem: null };
  if (!id) return vazio;
  try {
    const s = (armazem || window.sessionStorage).getItem(TD_CHAVE_ESTADO + id);
    const o = s ? JSON.parse(s) : null;
    if (!o || o.v !== 1) return vazio;
    const ordem = o.ordem && typeof o.ordem.col === 'string' && (o.ordem.dir === 'asc' || o.ordem.dir === 'desc') ? { col: o.ordem.col, dir: o.ordem.dir } : null;
    return { pagina: Math.max(1, parseInt(o.pagina, 10) || 1), porPagina: TD_POR_PAGINA.includes(o.porPagina) ? o.porPagina : null, ordem };
  } catch (e) { return vazio; }
}
function tdGravarEstado(id, c, armazem) {
  if (!id) return;
  try { (armazem || window.sessionStorage).setItem(TD_CHAVE_ESTADO + id, JSON.stringify({ v: 1, pagina: c.pagina, porPagina: c.porPagina, ordem: c.ordem || null })); } catch (e) { /* sem armazenamento: só não volta igual */ }
}
function tdLerOcultas(id, colunas, armazem) {
  const padrao = (colunas || []).filter((c) => c.oculta && !c.fixa).map((c) => c.id);
  if (!id) return padrao;
  try {
    const s = (armazem || window.localStorage).getItem(TD_CHAVE_COLUNAS + id);
    const o = s ? JSON.parse(s) : null;
    if (!Array.isArray(o)) return padrao;
    const ids = new Set((colunas || []).filter((c) => !c.fixa).map((c) => c.id));
    return o.filter((x) => ids.has(x));   // coluna que saiu do código, ou que virou fixa, não fica escondida para sempre
  } catch (e) { return padrao; }
}
function tdGravarOcultas(id, ocultas, armazem) {
  if (!id) return;
  try { (armazem || window.localStorage).setItem(TD_CHAVE_COLUNAS + id, JSON.stringify(ocultas)); } catch (e) { /* idem */ }
}

// ── o estado da consulta (página, linhas por página, ordem), guardado na aba ──────────────────────────────────
// Na lista paginada no SERVIDOR, quem busca é a tela: ela usa este hook, passa `consulta` e `onConsulta` à tabela, e
// depende de `consulta.pagina`, `consulta.porPagina` e `tdChaveOrdem(consulta.ordem)` (textos e números — nunca o
// objeto, que é novo a cada mudança).
function tdChaveOrdem(ordem) { return ordem ? ordem.col + ':' + ordem.dir : ''; }
function useConsultaTabela(idEstado, porPaginaPadrao) {
  const [c, setC] = React.useState(() => {
    const s = tdLerEstado(idEstado);
    return { pagina: s.pagina, porPagina: s.porPagina || (TD_POR_PAGINA.includes(porPaginaPadrao) ? porPaginaPadrao : TD_POR_PAGINA[0]), ordem: s.ordem };
  });
  const definir = React.useCallback((n) => {
    setC((a) => { const b = typeof n === 'function' ? n(a) : { ...a, ...n }; tdGravarEstado(idEstado, b); return b; });
  }, [idEstado]);
  return [c, definir];
}

// ── EstadoDados ─────────────────────────────────────────────────────────────────────────────────────────────
const ED_PADRAO = {
  carregando:    { icone: 'loader-2', titulo: 'Carregando…' },
  vazio:         { icone: 'inbox', titulo: 'Nada para mostrar' },
  erro:          { icone: 'alert-triangle', titulo: 'Não consegui carregar' },
  desatualizado: { icone: 'clock', titulo: 'Estes dados podem estar desatualizados' },
  sucesso:       { icone: 'check-circle-2', titulo: 'Feito' },
};
function EstadoDados({ estado, titulo, mensagem, aoTentar, rotuloTentar, lidoEm, acao, compacto }) {
  const p = ED_PADRAO[estado] || ED_PADRAO.vazio;
  const tentar = aoTentar ? (
    <Button variant="secondary" size="sm" icon="refresh-cw" onClick={aoTentar}>
      {rotuloTentar || (estado === 'desatualizado' ? 'Atualizar' : 'Tentar de novo')}
    </Button>
  ) : null;
  return (
    <div className={`ed ed-${estado || 'vazio'}${compacto ? ' ed-compacto' : ''}`} role={estado === 'erro' ? 'alert' : 'status'} aria-live="polite">
      <Icon name={p.icone} size={compacto ? 15 : 18} className={estado === 'carregando' ? 'ed-gira' : ''} />
      <div className="ed-texto">
        <div className="ed-titulo">{titulo || p.titulo}</div>
        {(mensagem || (estado === 'desatualizado' && lidoEm)) && (
          <div className="ed-msg">
            {mensagem}
            {estado === 'desatualizado' && lidoEm ? <>{mensagem ? ' · ' : ''}lidos em {fmtDatetime(lidoEm)}</> : null}
          </div>
        )}
      </div>
      {(tentar || acao) && <div className="ed-acoes">{acao}{tentar}</div>}
    </div>
  );
}

// ── PainelRegistro ──────────────────────────────────────────────────────────────────────────────────────────
// O registro de lado, com as ações DENTRO dele: a pessoa conclui a tarefa sem fechar o painel e procurar um ícone na
// lista (briefing § 6). A lista fica montada por baixo — fechar volta ao mesmo ponto, e o foco volta a quem abriu.
// `acoes`: [{ id, rotulo, onClick, pede, variante, icone, desabilitado, motivo }] — a última é a principal.
function PainelRegistro({ aberto, onFechar, titulo, subtitulo, eyebrow, acoes, estado, largura, children }) {
  const lista = Array.isArray(acoes) ? acoes.filter(Boolean) : [];
  const rodape = lista.length ? (
    <div className="pr-acoes">
      {lista.map((a, i) => (
        <Button key={a.id || i} variant={a.variante || (i === lista.length - 1 ? 'primary' : 'secondary')} icon={a.icone}
          pede={a.pede} disabled={!!a.desabilitado} title={a.desabilitado ? a.motivo : undefined} onClick={a.onClick}>
          {a.rotulo}
        </Button>
      ))}
    </div>
  ) : null;
  return (
    <Drawer open={!!aberto} onClose={onFechar} title={titulo} subtitle={subtitulo} eyebrow={eyebrow} footer={rodape} width={largura || 560}>
      {estado ? <EstadoDados {...estado} compacto /> : null}
      {children}
    </Drawer>
  );
}

// ── TabelaDados ─────────────────────────────────────────────────────────────────────────────────────────────
// colunas: [{ id, rotulo, valor(l) (o que ordena), render(l)?, num? (alinha à direita, sem quebrar "R$ 1.234,56"),
//             ordenavel? (padrão: sim, se tiver valor), oculta? (começa escondida), fixa? (não se esconde) }]
// Memória: passe `linhas` (todas). Servidor: passe `linhas` (só a página), `total` e `consulta`/`onConsulta`
// (do useConsultaTabela da tela, que é quem busca).
function TabelaDados({ idEstado, colunas, linhas, chave, total, consulta, onConsulta, porPaginaPadrao,
  selecionavel, acoesEmLote, onAbrir, rotuloLinha, estado, mensagemVazio, erro, aoTentar, desatualizado, lidoEm, rotulo }) {
  const cols = Array.isArray(colunas) ? colunas : [];
  const todas = Array.isArray(linhas) ? linhas : [];
  const chaveDe = React.useCallback((l) => String(chave ? chave(l) : l.id), [chave]);
  const servidor = typeof total === 'number' && !!onConsulta;
  const [consultaLocal, setConsultaLocal] = useConsultaTabela(idEstado, porPaginaPadrao);
  const c = servidor && consulta ? consulta : consultaLocal;
  const setC = servidor ? onConsulta : setConsultaLocal;
  const [ocultas, setOcultas] = React.useState(() => tdLerOcultas(idEstado, cols));
  const [sel, setSel] = React.useState(() => new Set());
  const [menuCol, setMenuCol] = React.useState(false);

  const visiveis = cols.filter((col) => col.fixa || !ocultas.includes(col.id));
  const colOrdem = c.ordem ? cols.find((col) => col.id === c.ordem.col) : null;
  const ordenadas = servidor ? todas : tdOrdenar(todas, colOrdem, c.ordem && c.ordem.dir);
  const pag = tdPaginar(servidor ? total : ordenadas.length, c.porPagina, c.pagina);
  const daPagina = servidor ? todas : ordenadas.slice(pag.inicio, pag.fim);
  const chavesDaPagina = daPagina.map(chaveDe);
  const selecionadas = todas.filter((l) => sel.has(chaveDe(l)));
  const estPagina = tdEstadoDaPagina(sel, chavesDaPagina);
  const caixaPag = React.useRef(null);
  React.useEffect(() => { if (caixaPag.current) caixaPag.current.indeterminate = estPagina === 'algumas'; }, [estPagina]);

  const alternarColuna = (id) => {
    setOcultas((a) => { const n = a.includes(id) ? a.filter((x) => x !== id) : a.concat([id]); tdGravarOcultas(idEstado, n); return n; });
  };
  const ordenarPor = (col) => setC((a) => ({ ...a, ordem: tdProximaOrdem(a.ordem, col.id), pagina: 1 }));
  const irPara = (p) => setC((a) => ({ ...a, pagina: p }));
  const mudarPorPagina = (pp) => setC((a) => ({ ...a, porPagina: pp, pagina: 1 }));
  const abrir = (l) => { if (onAbrir) onAbrir(l); };
  const nCols = visiveis.length + (selecionavel ? 1 : 0);
  const est = estado || (erro ? 'erro' : (!todas.length ? 'vazio' : null));

  return (
    <div className="td">
      {(desatualizado || cols.some((col) => !col.fixa)) && (
        <div className="td-topo">
          {desatualizado ? <EstadoDados estado="desatualizado" lidoEm={lidoEm} aoTentar={aoTentar} compacto /> : <span />}
          {cols.some((col) => !col.fixa) && (
            <div className="td-colunas">
              <button type="button" className="td-botao" aria-expanded={menuCol} aria-haspopup="true" onClick={() => setMenuCol((x) => !x)}>
                <Icon name="columns-3" size={14} /> Colunas{ocultas.length ? ` (${ocultas.length} oculta${ocultas.length !== 1 ? 's' : ''})` : ''}
              </button>
              {menuCol && (
                <div className="td-menu" role="group" aria-label="Mostrar ou ocultar colunas">
                  {cols.filter((col) => !col.fixa).map((col) => (
                    <label key={col.id} className="td-menu-item">
                      <input type="checkbox" checked={!ocultas.includes(col.id)} onChange={() => alternarColuna(col.id)} />
                      <span>{col.rotulo}</span>
                    </label>
                  ))}
                  <button type="button" className="td-botao td-menu-fechar" onClick={() => setMenuCol(false)}>Fechar</button>
                </div>
              )}
            </div>
          )}
        </div>
      )}

      {selecionavel && selecionadas.length > 0 && (
        <div className="td-lote" role="region" aria-label="Ações em lote">
          <b>{selecionadas.length} selecionad{selecionadas.length !== 1 ? 'os' : 'o'}</b>
          {servidor && <span className="muted">(desta página)</span>}
          <div className="td-lote-acoes">{acoesEmLote ? acoesEmLote(selecionadas, () => setSel(new Set())) : null}</div>
          <button type="button" className="td-botao" onClick={() => setSel(new Set())}>Limpar seleção</button>
        </div>
      )}

      <div className="td-caixa">
        <table className="tbl td-tabela" aria-label={rotulo}>
          <thead>
            <tr>
              {selecionavel && (
                <th className="td-sel" scope="col">
                  <input ref={caixaPag} type="checkbox" aria-label="Selecionar as linhas desta página"
                    checked={estPagina === 'todas' && chavesDaPagina.length > 0} disabled={!chavesDaPagina.length}
                    onChange={() => setSel((s) => tdMarcarPagina(s, chavesDaPagina, estPagina !== 'todas'))} />
                </th>
              )}
              {visiveis.map((col) => {
                const podeOrdenar = col.ordenavel !== false && typeof col.valor === 'function';
                const dir = c.ordem && c.ordem.col === col.id ? c.ordem.dir : null;
                return (
                  <th key={col.id} scope="col" className={col.num ? 'td-num' : ''}
                    aria-sort={dir === 'asc' ? 'ascending' : dir === 'desc' ? 'descending' : (podeOrdenar ? 'none' : undefined)}>
                    {podeOrdenar ? (
                      <button type="button" className="td-ordem" onClick={() => ordenarPor(col)}
                        title={`Ordenar por ${col.rotulo}${dir === 'asc' ? ' (decrescente)' : ''}`}>
                        {col.rotulo}
                        <Icon name={dir === 'asc' ? 'arrow-up' : dir === 'desc' ? 'arrow-down' : 'arrow-up-down'} size={12} className={dir ? '' : 'td-ordem-inativa'} />
                      </button>
                    ) : col.rotulo}
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {est ? (
              <tr className="td-estado"><td colSpan={nCols}>
                <EstadoDados estado={est} mensagem={est === 'erro' ? (erro || '') : (est === 'vazio' ? mensagemVazio : undefined)} aoTentar={est === 'erro' ? aoTentar : undefined} />
              </td></tr>
            ) : daPagina.map((l) => {
              const k = chaveDe(l);
              return (
                <tr key={k} className={onAbrir ? 'td-clicavel' : ''} tabIndex={onAbrir ? 0 : undefined}
                  aria-label={onAbrir && rotuloLinha ? rotuloLinha(l) : undefined}
                  onClick={onAbrir ? () => abrir(l) : undefined}
                  onKeyDown={onAbrir ? (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target === e.currentTarget) { e.preventDefault(); abrir(l); } } : undefined}>
                  {selecionavel && (
                    <td className="td-sel" onClick={(e) => e.stopPropagation()}>
                      <input type="checkbox" checked={sel.has(k)} aria-label={'Selecionar ' + (rotuloLinha ? rotuloLinha(l) : 'esta linha')}
                        onChange={() => setSel((s) => tdAlternar(s, k))} />
                    </td>
                  )}
                  {visiveis.map((col) => (
                    <td key={col.id} className={col.num ? 'td-num' : ''}>{col.render ? col.render(l) : (col.valor ? col.valor(l) : '')}</td>
                  ))}
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {!est && (
        <div className="td-rodape">
          <label className="td-pp">
            <span>Linhas por página</span>
            <select className="input" value={pag.porPagina} onChange={(e) => mudarPorPagina(Number(e.target.value))}>
              {TD_POR_PAGINA.map((n) => <option key={n} value={n}>{n}</option>)}
            </select>
          </label>
          <div className="td-pager"><FooterPager total={pag.total} perPage={pag.porPagina} page={pag.atual} onPage={irPara} /></div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, {
  TabelaDados, PainelRegistro, EstadoDados, useConsultaTabela,
  TD_POR_PAGINA, tdComparar, tdOrdenar, tdPaginar, tdProximaOrdem, tdAlternar, tdMarcarPagina, tdEstadoDaPagina,
  tdLerEstado, tdGravarEstado, tdLerOcultas, tdGravarOcultas, tdChaveOrdem,
});
