// =========================================================
// Configuracoes (settings) — view montada dentro do dashboard
// quando active === "configuracoes". Exporta global
// `ConfiguracoesView` (Babel standalone, sem modules).
// =========================================================

const { useState: cfgUseState, useMemo: cfgUseMemo, useEffect: cfgUseEffect, useCallback: cfgUseCallback } = React;

// ---------- mini icons (svgs locais; sem dependencia da paleta I) ----------
const CfgIcon = {
  back: (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" {...p}><path d="M15 18l-6-6 6-6"/></svg>,
  search: (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" {...p}><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3" strokeLinecap="round"/></svg>,
  company: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M3 21V7l9-4 9 4v14"/><path d="M9 9h6M9 13h6M9 17h6"/></svg>,
  locale: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg>,
  dashboard: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M3 12l4-4 4 4 4-7 6 9"/><path d="M3 21h18"/></svg>,
  pile: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/></svg>,
  charts: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M3 3v18h18"/><path d="M7 14l3-3 3 3 4-7"/></svg>,
  report: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M14 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><path d="M14 3v6h6M9 13h6M9 17h4"/></svg>,
  monitor: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><rect x="2" y="4" width="20" height="14" rx="2"/><path d="M8 21h8M12 18v3"/></svg>,
  map: (p) => <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.8" {...p}><path d="M12 2C8 2 5 5 5 9c0 5 7 13 7 13s7-8 7-13c0-4-3-7-7-7z"/><circle cx="12" cy="9" r="2.5"/></svg>,
  plus: (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" {...p}><path d="M12 5v14M5 12h14"/></svg>,
  check: (p) => <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" {...p}><path d="M5 13l4 4 10-10"/></svg>,
};

// ---------- estrutura das paginas (titulo + descricao do header) ----------
const CFG_PAGES = [
  { id: "company",   group: "Geral",                 icon: "company",   title: "Empresa e conta",        sub: "Logo, razão social, usuários",  eyebrow: "GERAL",                 desc: "Identidade da empresa, logo usado em relatórios e gestão de usuários." },
  { id: "locale",    group: "Geral",                 icon: "locale",    title: "Idioma e formato",       sub: "Locale, data, hora, timezone",  eyebrow: "GERAL",                 desc: "Como o sistema mostra números, datas, horários e moedas." },
  { id: "dashboard", group: "Dados e visualização",  icon: "dashboard", title: "Dashboard",              sub: "KPIs, períodos, polling",       eyebrow: "DADOS E VISUALIZAÇÃO",  desc: "Configure os KPIs principais, períodos de comparação e a frequência de atualização do dashboard." },
  { id: "piletable", group: "Dados e visualização",  icon: "pile",      title: "Últimas estacas executadas", sub: "Colunas, alertas, refresh",  eyebrow: "DADOS E VISUALIZAÇÃO",  desc: "Configure as colunas, faixas de classificação e alertas exibidos na tabela de últimas estacas executadas no dashboard." },
  { id: "charts",    group: "Dados e visualização",  icon: "charts",    title: "Gráficos da estaca",     sub: "Limites, séries, escalas",      eyebrow: "DADOS E VISUALIZAÇÃO",  desc: "Limites máximos, cores das séries e escalas usadas nos gráficos por profundidade." },
  { id: "report",    group: "Módulos",               icon: "report",    title: "Relatório PDF",          sub: "Rodapé e rótulos de assinatura",eyebrow: "MÓDULOS",               desc: "Textos fixos que aparecem em todas as páginas do PDF: rodapé e rótulos das assinaturas." },
  { id: "monitor",   group: "Módulos",               icon: "monitor",   title: "Monitoramento remoto",   sub: "Cards, polling, gauges",        eyebrow: "MÓDULOS",               desc: "Configurações da tela de monitoramento ao vivo das máquinas." },
  { id: "map",       group: "Módulos",               icon: "map",       title: "Mapa",                   sub: "Coordenadas, pins, pulsação",   eyebrow: "MÓDULOS",               desc: "Posição inicial, comportamento dos pins e animações." },
];

// ---------- subcomponents reutilizaveis ----------
function Segmented({ options, defaultIndex = 0, value, onChange }) {
  const [localIdx, setLocalIdx] = cfgUseState(defaultIndex);
  const idx = value !== undefined ? value : localIdx;
  return (
    <div className="segmented">
      {options.map((opt, i) => (
        <button
          key={i}
          className={i === idx ? "is-active" : ""}
          onClick={() => {
            if (value === undefined) setLocalIdx(i);
            onChange && onChange(opt, i);
          }}
        >{opt}</button>
      ))}
    </div>
  );
}

function Swatches({ colors, defaultIndex = 0, onChange, withAdd }) {
  const [idx, setIdx] = cfgUseState(defaultIndex);
  return (
    <div className="color-row">
      {colors.map((c, i) => (
        <button
          key={i}
          className={`swatch ${i === idx ? "is-active" : ""}`}
          style={{ background: c.bg }}
          title={c.title || ""}
          onClick={() => { setIdx(i); onChange && onChange(c, i); }}
        />
      ))}
      {withAdd && (
        <button className="swatch-add" title="Personalizada">
          <CfgIcon.plus/>
        </button>
      )}
    </div>
  );
}

function Toggle({ defaultChecked, onChange }) {
  const [on, setOn] = cfgUseState(!!defaultChecked);
  return (
    <label className="toggle">
      <input
        type="checkbox"
        checked={on}
        onChange={(e) => { setOn(e.target.checked); onChange && onChange(e.target.checked); }}
      />
      <span className="toggle-track"/>
    </label>
  );
}

function Row({ title, desc, control, stacked }) {
  return (
    <div className={`set-row ${stacked ? "is-stacked" : ""}`}>
      <div className="set-row-label">
        <div className="set-row-title">{title}</div>
        {desc && <div className="set-row-desc">{desc}</div>}
      </div>
      <div className="set-row-control">{control}</div>
    </div>
  );
}

function Section({ title, children }) {
  return (
    <div className="set-section">
      <h3 className="set-section-title">{title}</h3>
      <div className="set-card">{children}</div>
    </div>
  );
}

function PageHead({ eyebrow, title, desc }) {
  return (
    <div className="set-page-head">
      <div className="set-page-eyebrow">{eyebrow}</div>
      <h1 className="set-page-title">{title}</h1>
      <p className="set-page-desc">{desc}</p>
    </div>
  );
}

// =========================================================
// Paginas
// =========================================================

// Iniciais de 1-2 letras a partir do nome ou email — usadas no avatar
// circular dos usuarios listados em "Empresa e conta".
function iniciaisDoUsuario(u) {
  const fonte = (u.nome || u.email || '?').trim();
  const partes = fonte.split(/[\s@._-]+/).filter(Boolean);
  if (partes.length >= 2) {
    return (partes[0][0] + partes[1][0]).toUpperCase();
  }
  return (partes[0] || '?').slice(0, 2).toUpperCase();
}

// Cor estavel a partir do email — paleta pequena para nao gerar cores
// muito proximas do tema accent.
const CORES_AVATAR = ['#3B6EE0', '#7B61D6', '#22A06B', '#E08B2A', '#D14343', '#0EA5A4', '#DB2777', '#5A6478'];
function corDoUsuario(u) {
  const s = String(u.email || u.nome || '');
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;
  return CORES_AVATAR[h % CORES_AVATAR.length];
}

const PAPEL_CHIP = {
  admin_sistema: { label: 'Admin sistema', estilo: { background: 'var(--accent-soft)', color: 'var(--accent-deep)' } },
  admin_cliente: { label: 'Admin cliente', estilo: { background: 'rgba(123,97,214,.12)', color: '#7B61D6' } },
  usuario:       { label: 'Usuário',       estilo: {} },
};

// Tenta extrair "Dispositivo · Navegador" do user-agent. Heuristica simples
// — UA strings sao caoticas, mas nao precisa de uma lib pra acertar 95% dos
// casos comuns. Fallback: substring do UA cru (truncado).
function descreverUserAgent(ua) {
  if (!ua) return 'Dispositivo desconhecido';
  const s = String(ua);
  let dispositivo = '';
  let navegador = '';
  if (/iPhone/i.test(s)) dispositivo = 'iPhone';
  else if (/iPad/i.test(s)) dispositivo = 'iPad';
  else if (/Android/i.test(s)) dispositivo = 'Android';
  else if (/Macintosh|Mac OS X/i.test(s)) dispositivo = 'Mac';
  else if (/Windows NT 10/i.test(s)) dispositivo = 'Windows';
  else if (/Windows/i.test(s)) dispositivo = 'Windows';
  else if (/Linux/i.test(s)) dispositivo = 'Linux';

  if (/Edg\//i.test(s)) navegador = 'Edge';
  else if (/Chrome\/[\d.]+ Mobile/i.test(s)) navegador = 'Chrome Mobile';
  else if (/Chrome\/[\d.]+/i.test(s) && !/OPR\//i.test(s)) navegador = 'Chrome';
  else if (/Firefox\//i.test(s)) navegador = 'Firefox';
  else if (/Version\/[\d.]+.*Safari/i.test(s)) navegador = 'Safari';
  else if (/Safari\//i.test(s)) navegador = 'Safari';
  else if (/OPR\//i.test(s)) navegador = 'Opera';

  if (dispositivo && navegador) return `${dispositivo} · ${navegador}`;
  if (dispositivo) return dispositivo;
  if (navegador) return navegador;
  return s.length > 60 ? s.slice(0, 60) + '…' : s;
}

function formatarDataHoraSet(ts) {
  if (!ts) return '—';
  const d = new Date(ts);
  if (!Number.isFinite(d.getTime())) return '—';
  const p = (n) => String(n).padStart(2, '0');
  return `${p(d.getDate())}/${p(d.getMonth() + 1)}/${d.getFullYear()} ${p(d.getHours())}:${p(d.getMinutes())}`;
}

// Lista as sessoes ativas do usuario logado (max 2 hoje, por causa do
// limite no /api/login). Permite encerrar remotamente — se o usuario
// encerrar a propria sessao atual, o front recarrega pra cair no login.
function MinhasSessoes() {
  const [sessoes, setSessoes] = cfgUseState(null);
  const [erro, setErro] = cfgUseState(null);
  const [encerrando, setEncerrando] = cfgUseState(null); // jti em curso

  const recarregar = React.useCallback(() => {
    setErro(null);
    fetch('/api/minhas-sessoes', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.erro || `Erro ${r.status}`)))
      .then(arr => setSessoes(Array.isArray(arr) ? arr : []))
      .catch(e => { setErro(String(e || 'Falha ao listar sessões.')); setSessoes([]); });
  }, []);

  React.useEffect(() => { recarregar(); }, [recarregar]);

  const encerrar = async (s) => {
    const msg = s.atual
      ? 'Encerrar a sessão ATUAL? Você será desconectado deste dispositivo.'
      : `Encerrar a sessão em "${descreverUserAgent(s.user_agent)}"?`;
    if (!window.confirm(msg)) return;
    setEncerrando(s.jti);
    try {
      const r = await fetch(`/api/minhas-sessoes/${encodeURIComponent(s.jti)}`, {
        method: 'DELETE',
        credentials: 'same-origin',
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) throw new Error(j.erro || `Erro ${r.status}`);
      if (j.eraAtual) {
        // Recarrega a pagina inteira — sem o jti em sessoes_ativas, a proxima
        // requisicao /api/me ja devolve 401 e o front cai no login.
        window.location.reload();
        return;
      }
      recarregar();
    } catch (e) {
      alert('Falha ao encerrar sessão: ' + (e.message || e));
    } finally {
      setEncerrando(null);
    }
  };

  return (
    <Section title="Minhas sessões">
      <Row
        stacked
        title="Dispositivos conectados"
        desc="Limite de 2 sessões simultâneas. Encerre uma sessão para liberar espaço em outro dispositivo."
        control={
          <div className="minhas-sessoes-lista" style={{ width: '100%' }}>
            {sessoes === null && (
              <div style={{ padding: 12, color: 'var(--muted)' }}>Carregando…</div>
            )}
            {erro && (
              <div style={{ padding: 12, color: '#D14343' }}>{erro}</div>
            )}
            {sessoes && sessoes.length === 0 && !erro && (
              <div style={{ padding: 12, color: 'var(--muted)' }}>Nenhuma sessão ativa encontrada.</div>
            )}
            {sessoes && sessoes.map(s => (
              <div key={s.jti} className="minhas-sessoes-item">
                <div className="minhas-sessoes-info">
                  <div className="minhas-sessoes-disp">
                    {descreverUserAgent(s.user_agent)}
                    {s.atual && <span className="minhas-sessoes-tag-atual">Atual</span>}
                  </div>
                  <div className="minhas-sessoes-meta">
                    <span>IP: <code>{s.ip || '—'}</code></span>
                    <span>Último uso: {formatarDataHoraSet(s.ultimo_uso || s.criada_em)}</span>
                    <span>Expira: {formatarDataHoraSet(s.expira_em)}</span>
                  </div>
                </div>
                <button
                  type="button"
                  className="btn btn-ghost"
                  disabled={encerrando === s.jti}
                  onClick={() => encerrar(s)}
                >
                  {encerrando === s.jti ? 'Encerrando…' : 'Encerrar'}
                </button>
              </div>
            ))}
          </div>
        }
      />
    </Section>
  );
}

function CfgPageCompany({ cfg, atualizarSecao, podeEditar, podeEditarEmpresa, papel, notify, accent }) {
  const empresa = (cfg && cfg.empresa) || {};
  // `ro` da pagina Empresa olha pra permissao de empresa especificamente
  // (logo/razao/CNPJ + secao de Usuarios). Componentes pessoais como
  // <MinhasSessoes/> nao usam essa flag.
  const ro = !(podeEditarEmpresa ?? podeEditar);
  // Fallback simples quando o componente e' montado sem `notify`/`accent`
  // (defesa). O fluxo normal recebe ambos da Dashboard via ConfiguracoesView.
  const aviso = notify || ((msg) => alert(msg));
  const accentSeguro = accent || { hex: '#3B6EE0' };

  // Logo: refresca o preview via cache-buster apos upload/remoção.
  const [logoBust, setLogoBust] = cfgUseState(Date.now());
  const [logoExiste, setLogoExiste] = cfgUseState(true); // assume true; <img onError> corrige
  const fileInputRef = React.useRef(null);

  const handleLogoUpload = async (file) => {
    if (!file || ro) return;
    const fd = new FormData();
    fd.append('logo', file);
    try {
      const r = await fetch('/api/logo', { method: 'POST', credentials: 'same-origin', body: fd });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      setLogoBust(Date.now());
      setLogoExiste(true);
    } catch (err) {
      alert('Falha ao enviar logo: ' + (err.message || err));
    }
  };
  const handleLogoRemove = async () => {
    if (ro) return;
    if (!window.confirm('Remover o logo da empresa?')) return;
    try {
      const r = await fetch('/api/logo', { method: 'DELETE', credentials: 'same-origin' });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      setLogoBust(Date.now());
      setLogoExiste(false);
    } catch (err) {
      alert('Falha ao remover logo: ' + (err.message || err));
    }
  };

  // Lista de usuarios da empresa. Carrega via /api/usuarios, que ja
  // filtra pelo cliente do JWT quando o solicitante e' admin_cliente.
  // `refreshUsuarios` re-emite o fetch apos editar/desativar/reativar.
  const [usuarios, setUsuarios] = cfgUseState(null); // null = carregando, [] = vazio
  const [erroUsuarios, setErroUsuarios] = cfgUseState(null);
  const [refreshUsuarios, setRefreshUsuarios] = cfgUseState(0);
  // null = fechado, objeto-usuario = editando aquele.
  const [modalUsuario, setModalUsuario] = cfgUseState(null);
  // E-mail do solicitante — usado pra esconder o botao "Desativar" da
  // propria linha (admin_cliente nao pode se auto-desativar; quem faz
  // isso e' o admin_sistema).
  const [meuEmail, setMeuEmail] = cfgUseState('');
  cfgUseEffect(() => {
    let vivo = true;
    fetch('/api/me', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(j => { if (vivo && j && j.email) setMeuEmail(String(j.email).toLowerCase()); })
      .catch(() => {});
    return () => { vivo = false; };
  }, []);
  cfgUseEffect(() => {
    let vivo = true;
    setErroUsuarios(null);
    fetch('/api/usuarios', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.erro || `HTTP ${r.status}`)))
      .then(arr => { if (vivo) setUsuarios(Array.isArray(arr) ? arr : []); })
      .catch(e => { if (vivo) { setUsuarios([]); setErroUsuarios(String(e || 'Falha ao carregar usuários')); } });
    return () => { vivo = false; };
  }, [refreshUsuarios]);
  const ativos = usuarios ? usuarios.filter(u => u.ativo).length : 0;

  // admin_cliente e admin_sistema podem editar; usuario comum nao chega
  // aqui pq o save bar e o restante da pagina ja respeitam podeEditar.
  // O backend valida o resto (admin_cliente nao muda empresa, nao promove
  // a admin_sistema, nao desativa a propria conta — espelhamos as msgs).
  const podeGerenciar = papel === 'admin_sistema' || papel === 'admin_cliente';

  const alternarAtivo = async (u) => {
    const acao = u.ativo ? 'desativar' : 'reativar';
    if (!window.confirm(`Confirma ${acao} o usuário ${u.email}?`)) return;
    try {
      const r = await fetch(`/api/usuarios/${u.id}`, {
        method: 'PATCH',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ativo: !u.ativo }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) throw new Error(j.erro || `Erro ${r.status}`);
      aviso(`Usuário ${acao === 'desativar' ? 'desativado' : 'reativado'}.`, 'ok');
      setRefreshUsuarios(k => k + 1);
    } catch (e) {
      aviso(`Falha ao ${acao}: ${e.message || e}`, 'erro');
    }
  };

  // Helpers de patch
  const setRazao = (v) => atualizarSecao('empresa', { ...empresa, razaoSocial: v });
  const setCnpj  = (v) => atualizarSecao('empresa', { ...empresa, cnpj: v });

  // Mascara simples de CNPJ pra exibicao (NN.NNN.NNN/NNNN-NN). Aceita
  // tanto a forma com mascara quanto so digitos no salvar — a logica
  // que consome trata os dois.
  const mascararCnpj = (s) => {
    const d = String(s || '').replace(/\D/g, '').slice(0, 14);
    if (d.length <= 2) return d;
    if (d.length <= 5) return `${d.slice(0,2)}.${d.slice(2)}`;
    if (d.length <= 8) return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5)}`;
    if (d.length <= 12) return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5,8)}/${d.slice(8)}`;
    return `${d.slice(0,2)}.${d.slice(2,5)}.${d.slice(5,8)}/${d.slice(8,12)}-${d.slice(12)}`;
  };

  return (
    <>
      <Section title="Identidade">
        <Row
          stacked
          title="Logo da empresa"
          desc="Usado nos relatórios PDF. Recomendado: PNG ou SVG, fundo transparente, mínimo 256×256."
          control={
            <div style={{ width: "100%", display: "grid", gap: 10 }}>
              <div className="logo-upload-current">
                <div className="logo-thumb" style={{ overflow: 'hidden', padding: 0 }}>
                  {logoExiste ? (
                    <img
                      src={`/api/logo?t=${logoBust}`}
                      alt="Logo"
                      style={{ width: '100%', height: '100%', objectFit: 'contain' }}
                      onError={() => setLogoExiste(false)}
                      onLoad={() => setLogoExiste(true)}
                    />
                  ) : (
                    <span>—</span>
                  )}
                </div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontWeight: 600, fontSize: 13 }}>
                    {logoExiste ? 'Logo personalizado' : 'Sem logo configurado'}
                  </div>
                  <div style={{ fontSize: 11.5, color: "var(--muted)" }}>
                    {logoExiste ? 'Aparece nos PDFs gerados.' : 'Use o botão ao lado para enviar.'}
                  </div>
                </div>
                <input
                  ref={fileInputRef}
                  type="file"
                  accept="image/*"
                  style={{ display: 'none' }}
                  onChange={(e) => { handleLogoUpload(e.target.files?.[0]); e.target.value = ''; }}
                />
                <button className="btn btn-ghost" disabled={ro} onClick={() => fileInputRef.current?.click()}>
                  {logoExiste ? 'Substituir' : 'Enviar'}
                </button>
                {logoExiste && (
                  <button className="btn btn-danger" disabled={ro} onClick={handleLogoRemove}>Remover</button>
                )}
              </div>
            </div>
          }
        />
        <Row
          title="Razão social"
          desc="Nome jurídico exibido em relatórios."
          control={
            <input className="input" style={{ width: 320 }} disabled={ro}
                   value={empresa.razaoSocial || ''}
                   onChange={(e) => setRazao(e.target.value)}/>
          }
        />
        <Row
          title="CNPJ"
          desc="Apenas para identificação fiscal."
          control={
            <input className="input" style={{ width: 200, fontFamily: "'JetBrains Mono',monospace" }} disabled={ro}
                   value={mascararCnpj(empresa.cnpj || '')}
                   onChange={(e) => setCnpj(mascararCnpj(e.target.value))}/>
          }
        />
      </Section>

      <MinhasSessoes/>

      <div className="set-section">
        <h3 className="set-section-title">
          Usuários{usuarios !== null ? ` · ${ativos} ativos` : ''}
        </h3>
        {usuarios === null ? (
          <div className="set-card" style={{ padding: 20, color: 'var(--muted)' }}>Carregando…</div>
        ) : erroUsuarios ? (
          <div className="set-card" style={{ padding: 20, color: 'var(--alert)' }}>{erroUsuarios}</div>
        ) : usuarios.length === 0 ? (
          <div className="set-card" style={{ padding: 20, color: 'var(--muted)' }}>Nenhum usuário cadastrado nesta empresa.</div>
        ) : (
          <div className="set-card">
            {usuarios.map((u) => {
              const chip = PAPEL_CHIP[u.papel] || PAPEL_CHIP.usuario;
              const sub = [
                u.email,
                u.ultimo_login
                  ? `último acesso ${new Date(u.ultimo_login).toLocaleString('pt-BR')}`
                  : 'nunca acessou',
                u.ativo ? null : 'inativo',
              ].filter(Boolean).join(' · ');
              return (
                <div className="set-row" key={u.id}>
                  <div className="set-row-label" style={{ display: "flex", alignItems: "center", gap: 12 }}>
                    <div className="user-pill" style={{ background: corDoUsuario(u), opacity: u.ativo ? 1 : 0.5 }}>
                      {iniciaisDoUsuario(u)}
                    </div>
                    <div>
                      <div className="set-row-title">
                        {u.nome || u.email}
                        {" · "}
                        <span className="chip" style={chip.estilo}>{chip.label}</span>
                      </div>
                      <div className="set-row-desc">{sub}</div>
                    </div>
                  </div>
                  {podeGerenciar && (() => {
                    // Auto-desativacao e' bloqueada pelo backend; aqui
                    // escondemos o botao pra deixar a UI consistente.
                    // Reativar tambem nao faz sentido na propria linha
                    // (a propria conta sempre esta ativa pra estar logada).
                    const ehMinhaLinha = !!meuEmail
                      && String(u.email || '').toLowerCase() === meuEmail;
                    return (
                      <div className="set-row-control" style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                        <button
                          type="button"
                          className="usuarios-btn-acao"
                          onClick={() => setModalUsuario(u)}
                          title="Editar dados do usuário"
                        >
                          Editar
                        </button>
                        {!ehMinhaLinha && (
                          <button
                            type="button"
                            className="usuarios-btn-acao"
                            onClick={() => alternarAtivo(u)}
                            title={u.ativo ? 'Desativar usuário' : 'Reativar usuário'}
                          >
                            {u.ativo ? 'Desativar' : 'Reativar'}
                          </button>
                        )}
                      </div>
                    );
                  })()}
                </div>
              );
            })}
          </div>
        )}
        {papel === 'admin_cliente' && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'var(--muted)' }}>
            Você pode editar e desativar usuários da sua empresa. Para criar novos usuários, contate o administrador do sistema.
          </div>
        )}
        {papel === 'admin_sistema' && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'var(--muted)' }}>
            Para criar novos usuários ou navegar entre empresas, use a tela <strong>Usuários</strong> no menu lateral.
          </div>
        )}
      </div>

      {modalUsuario && (() => {
        // UsuarioFormModal e' definido em login-dash.jsx (carregado depois
        // deste arquivo). Lemos do window no momento do render — nesse
        // ponto o componente ja existe.
        const Modal = window.UsuarioFormModal;
        if (!Modal) return null;
        return (
          <Modal
            accent={accentSeguro}
            notify={aviso}
            usuario={modalUsuario}
            papelDoLogado={papel}
            onClose={() => setModalUsuario(null)}
            onSalvo={() => { setModalUsuario(null); setRefreshUsuarios(k => k + 1); }}
          />
        );
      })()}
    </>
  );
}

function CfgPageLocale({ flagDirty }) {
  return (
    <>
      <Section title="Locale">
        <Row
          title="Idioma"
          desc="Define a linguagem de toda a interface."
          control={
            <select className="select" style={{ width: 180 }} onChange={flagDirty} defaultValue="pt-BR">
              <option value="pt-BR">Português (Brasil)</option>
              <option value="en-US">English (US)</option>
              <option value="es-LA">Español (Latam)</option>
            </select>
          }
        />
        <Row
          title="Fuso horário"
          desc="Usado em todos os timestamps, gráficos e relatórios."
          control={
            <select className="select" style={{ width: 240 }} onChange={flagDirty} defaultValue="sp">
              <option value="sp">America/Sao_Paulo · BRT (UTC-3)</option>
              <option value="ma">America/Manaus · AMT (UTC-4)</option>
              <option value="utc">UTC</option>
            </select>
          }
        />
      </Section>
    </>
  );
}

// Lista canonica dos KPIs. ID bate com cfg.dashboard.kpis[].id.
const KPIS_DASHBOARD_DEFS = [
  { id: 'estacas',  cor: '#3B6EE0', nome: 'Estacas',         formula: 'SUM(estacas)' },
  { id: 'metros',   cor: '#7B61D6', nome: 'Metros',          formula: 'SUM(profundidade)' },
  { id: 'concreto', cor: '#22A06B', nome: 'Concreto',        formula: 'SUM(volume_l)' },
  { id: 'maquinas', cor: '#E08B2A', nome: 'Máquinas ativas', formula: 'COUNT(status=ativa)' },
];

function CfgPageDashboard({ cfg, atualizarSecao, podeEditar }) {
  const dash = (cfg && cfg.dashboard) || {};
  const kpisCfg = Array.isArray(dash.kpis) ? dash.kpis : [];
  const prod = dash.producaoMensal || {};
  const ro = !podeEditar;

  // Lista de maquinas pra popular o select de Maquina padrao. Carrega
  // do mesmo endpoint que o dashboard usa.
  const [maquinas, setMaquinas] = cfgUseState([]);
  cfgUseEffect(() => {
    let vivo = true;
    fetch('/api/maquinas-producao', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : [])
      .then(arr => { if (vivo && Array.isArray(arr)) setMaquinas(arr); })
      .catch(() => {});
    return () => { vivo = false; };
  }, []);

  // Reconcilia o array de KPIs salvo com a lista canonica:
  //   - mantem ordem do salvo
  //   - filtra IDs desconhecidos
  //   - acrescenta no fim os IDs canonicos ausentes (visivel:true)
  const kpisOrdem = (() => {
    const visto = new Set();
    const saida = [];
    for (const k of kpisCfg) {
      if (!k || !k.id) continue;
      const def = KPIS_DASHBOARD_DEFS.find(d => d.id === k.id);
      if (!def || visto.has(def.id)) continue;
      visto.add(def.id);
      saida.push({ id: def.id, visivel: k.visivel !== false, def });
    }
    for (const def of KPIS_DASHBOARD_DEFS) {
      if (!visto.has(def.id)) saida.push({ id: def.id, visivel: true, def });
    }
    return saida;
  })();

  const setKpis = (arr) => atualizarSecao('dashboard', {
    kpis: arr.map(k => ({ id: k.id, visivel: !!k.visivel })),
  });
  const moverKpi = (idx, delta) => {
    const novo = kpisOrdem.slice();
    const j = idx + delta;
    if (j < 0 || j >= novo.length) return;
    [novo[idx], novo[j]] = [novo[j], novo[idx]];
    setKpis(novo);
  };
  const toggleKpi = (idx) => {
    const novo = kpisOrdem.slice();
    novo[idx] = { ...novo[idx], visivel: !novo[idx].visivel };
    setKpis(novo);
  };

  const setProd = (chave, valor) => atualizarSecao('dashboard', {
    producaoMensal: { ...prod, [chave]: valor },
  });

  // Opcoes de periodo dependem da granularidade.
  const granAtual = prod.granularidadePadrao || 'mensal';
  const periodos = granAtual === 'diaria'
    ? [7, 14, 30, 60, 90]
    : [3, 6, 9, 12, 24, 36, 48];
  const labelPeriodo = (n) => granAtual === 'diaria' ? `${n} dias` : `${n} meses`;

  const metricaPadrao = prod.metricaPadrao || 'estacas';

  return (
    <>
      <Section title="KPIs principais">
        <Row
          stacked
          title="Cards exibidos no topo"
          desc="Use ↑/↓ para reordenar e o switch para esconder."
          control={
            <div style={{ width: "100%" }}>
              <div className="kpi-list">
                {kpisOrdem.map((k, i) => (
                  <div className="kpi-item" key={k.id} style={{ opacity: k.visivel ? 1 : 0.55 }}>
                    <div style={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
                      <button type="button" disabled={ro || i === 0}
                              onClick={() => moverKpi(i, -1)}
                              style={{ border: 0, background: 'transparent', cursor: (ro || i === 0) ? 'default' : 'pointer', color: 'var(--muted)', padding: '0 4px', lineHeight: 1, fontSize: 11 }}
                              title="Subir">▲</button>
                      <button type="button" disabled={ro || i === kpisOrdem.length - 1}
                              onClick={() => moverKpi(i, 1)}
                              style={{ border: 0, background: 'transparent', cursor: (ro || i === kpisOrdem.length - 1) ? 'default' : 'pointer', color: 'var(--muted)', padding: '0 4px', lineHeight: 1, fontSize: 11 }}
                              title="Descer">▼</button>
                    </div>
                    <span className="kpi-color" style={{ background: k.def.cor }}/>
                    <span className="kpi-name">{k.def.nome}</span>
                    <span className="kpi-formula">{k.def.formula}</span>
                    <label className="toggle">
                      <input type="checkbox" checked={k.visivel} disabled={ro}
                             onChange={() => toggleKpi(i)}/>
                      <span className="toggle-track"/>
                    </label>
                  </div>
                ))}
              </div>
            </div>
          }
        />
      </Section>

      <Section title="Produção mensal">
        <Row
          title="Métrica padrão"
          desc="Qual métrica aparece selecionada por padrão."
          control={
            <Segmented
              options={["Estacas", "Metros", "Concreto"]}
              value={['estacas', 'metros', 'concreto'].indexOf(metricaPadrao)}
              onChange={(_, i) => !ro && setProd('metricaPadrao', ['estacas', 'metros', 'concreto'][i])}
            />
          }
        />
        <Row
          title="Granularidade padrão"
          desc="Mensal (vista de período longo) ou Diária (últimos N dias)."
          control={
            <Segmented
              options={["Mensal", "Diária"]}
              value={granAtual === 'diaria' ? 1 : 0}
              onChange={(_, i) => {
                if (ro) return;
                const novaGran = i === 0 ? 'mensal' : 'diaria';
                // Ao trocar granularidade, periodo padrao tambem muda
                // pra ficar coerente.
                atualizarSecao('dashboard', {
                  producaoMensal: {
                    ...prod,
                    granularidadePadrao: novaGran,
                    periodoPadrao: novaGran === 'diaria' ? 30 : 6,
                  },
                });
              }}
            />
          }
        />
        <Row
          title="Período padrão"
          desc={granAtual === 'diaria' ? 'Quantos dias mostrar no gráfico.' : 'Quantos meses mostrar no gráfico.'}
          control={
            <select className="select" style={{ width: 140 }} disabled={ro}
                    value={Number(prod.periodoPadrao) || (granAtual === 'diaria' ? 30 : 6)}
                    onChange={(e) => setProd('periodoPadrao', Number(e.target.value))}>
              {periodos.map(n => (
                <option key={n} value={n}>{labelPeriodo(n)}</option>
              ))}
            </select>
          }
        />
        <Row
          title="Máquina padrão"
          desc="Qual máquina aparece pré-selecionada no dropdown."
          control={
            <select className="select" style={{ width: 220 }} disabled={ro}
                    value={prod.maquinaPadrao || ''}
                    onChange={(e) => setProd('maquinaPadrao', e.target.value)}>
              <option value="">Todas as máquinas</option>
              {maquinas.map(m => (
                <option key={m.numSerie} value={m.numSerie}>
                  {m.tag ? `${m.tag} / ${m.numSerie}` : `${m.numSerie}`}
                </option>
              ))}
            </select>
          }
        />
      </Section>

      <Section title="Obras">
        <Row
          title="Detectar obras não cadastradas"
          desc="Avisa quando chega um arquivo novo cuja obra ainda não foi cadastrada e oferece o cadastro na hora. Desligue se preferir não receber esses avisos."
          control={
            <label className="toggle">
              <input
                type="checkbox"
                checked={dash.detectarObrasOrfas !== false}
                disabled={ro}
                onChange={(e) => atualizarSecao('dashboard', { detectarObrasOrfas: e.target.checked })}
              />
              <span className="toggle-track"/>
            </label>
          }
        />
      </Section>
    </>
  );
}

// Lista canonica das colunas que podem aparecer na tabela do dashboard.
// A ordem aqui define a ordem de exibicao no toggle de "Colunas visiveis".
const COLUNAS_TABELA_ESTACAS = [
  "Fim concretagem", "Tag", "Obra", "Estaca", "Diâmetro", "Profundidade", "Volume",
  "Superconsumo",
  "Concreto", "Perfil", "Torque", "Rotação", "X / Y",
];

const COR_ESTADO_ALERTA = { ok: "#22A06B", atencao: "#E08B2A", alerta: "#D14343" };
const LABEL_ESTADO_ALERTA = { ok: "Verde", atencao: "Amarelo", alerta: "Vermelho" };
// Nome semântico do estado, usado na narrativa "$NOME até X" das tiras
// estilo card de Faixas de superconsumo.
const NOME_ESTADO_ALERTA = { ok: "Ideal", atencao: "Atenção", alerta: "Alerta" };

function CfgPagePileTable({ cfg, atualizarSecao, podeEditar }) {
  const tab = (cfg && cfg.tabelaEstacas) || {};
  const colunasVisiveis = tab.colunasVisiveis || {};
  const faixas = tab.faixasSuperConsumo || { minAtencao: 15, minOk: 25, maxOk: 50 };
  // O id "consumo" foi descontinuado — agora a classificacao de
  // superconsumo vai pra propria coluna Superconsumo (badge colorida).
  // Filtra fora pra nao mostrar o card redundante na tela de configs
  // mesmo em empresas que ja tinham esse alerta salvo no JSONB.
  const alertasColunas = ((tab.alertas && tab.alertas.colunas) || [])
    .filter(c => c && c.id !== 'consumo');

  const ro = !podeEditar;

  // helpers de patch — sempre criam novos objetos/arrays (imutavel)
  const setCampo = (campo, valor) => atualizarSecao('tabelaEstacas', { [campo]: valor });
  const setColunaVis = (nome, on) => atualizarSecao('tabelaEstacas', {
    colunasVisiveis: { ...colunasVisiveis, [nome]: on },
  });
  const setFaixa = (chave, valor) => atualizarSecao('tabelaEstacas', {
    faixasSuperConsumo: { ...faixas, [chave]: valor },
  });
  const setAlerta = (idx, patch) => {
    const novas = alertasColunas.map((c, i) => i === idx ? { ...c, ...patch } : c);
    atualizarSecao('tabelaEstacas', { alertas: { ...(tab.alertas || {}), colunas: novas } });
  };
  const setFaixaAlerta = (colIdx, faixaIdx, patch) => {
    const col = alertasColunas[colIdx];
    if (!col || !Array.isArray(col.faixas)) return;
    const novasFaixas = col.faixas.map((f, i) => i === faixaIdx ? { ...f, ...patch } : f);
    setAlerta(colIdx, { faixas: novasFaixas });
  };

  const numOuVazio = (s) => s === '' || s == null ? '' : Number(s);

  // 4 niveis. Tanto valores muito baixos quanto muito altos sao alerta
  // (vermelho); a faixa do meio e' a ideal (verde) com uma zona de
  // atencao (amarelo) entre alerta inferior e ideal.
  const tiras = [
    { bar: "#D14343", name: "Alerta",  range: `< ${faixas.minAtencao}%` },
    { bar: "#E08B2A", name: "Atenção", range: `${faixas.minAtencao} – ${faixas.minOk}%` },
    { bar: "#22A06B", name: "Ideal",   range: `${faixas.minOk} – ${faixas.maxOk}%` },
    { bar: "#D14343", name: "Alerta",  range: `> ${faixas.maxOk}%` },
  ];

  return (
    <>
      <Section title="Apresentação">
        <Row
          title="Limite de linhas exibidas"
          desc="Quantas estacas aparecem na tabela. Mínimo 1, máximo 500."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" min="1" max="500" disabled={ro}
                     value={tab.limiteLinhas ?? ''}
                     onChange={(e) => setCampo('limiteLinhas', numOuVazio(e.target.value))}
                     onBlur={(e) => {
                       // Clamp 1..500 ao perder o foco — `max` HTML so
                       // limita os spinners, nao o teclado.
                       const v = Number(e.target.value);
                       if (!Number.isFinite(v)) { setCampo('limiteLinhas', 100); return; }
                       const clamped = Math.min(500, Math.max(1, Math.floor(v)));
                       if (clamped !== v) setCampo('limiteLinhas', clamped);
                     }}/>
              <span className="input-suffix">linhas</span>
            </div>
          }
        />
        <Row
          stacked
          title="Colunas visíveis"
          desc="Marque as colunas que devem aparecer na tabela."
          control={
            <div style={{ width: "100%", display: "grid", gridTemplateColumns: "repeat(3,1fr)", gap: 8 }}>
              {COLUNAS_TABELA_ESTACAS.map((nome) => (
                <label className="kpi-item" style={{ cursor: ro ? 'default' : 'pointer' }} key={nome}>
                  <span className="kpi-name">{nome}</span>
                  <label className="toggle">
                    <input
                      type="checkbox"
                      checked={!!colunasVisiveis[nome]}
                      disabled={ro}
                      onChange={(e) => setColunaVis(nome, e.target.checked)}
                    />
                    <span className="toggle-track"/>
                  </label>
                </label>
              ))}
            </div>
          }
        />
      </Section>

      <Section title="Faixas de superconsumo">
        <Row
          stacked
          title="Limites de classificação (%)"
          desc="Define a cor da badge na coluna Superconsumo da tabela. Tanto valores muito baixos quanto muito altos disparam alerta."
          control={
            <div style={{ width: "100%" }}>
              <div className="thresh" style={{ gridTemplateColumns: "repeat(4, 1fr)" }}>
                {tiras.map((t, i) => (
                  <div className="thresh-cell" key={i}>
                    <div className="thresh-bar" style={{ background: t.bar }}/>
                    <div className="thresh-name">{t.name}</div>
                    <div className="thresh-range">{t.range}</div>
                  </div>
                ))}
              </div>
              <div className="thresh-inputs">
                Alerta abaixo de{" "}
                <input className="input" type="number" disabled={ro} value={faixas.minAtencao ?? ''}
                       onChange={(e) => setFaixa('minAtencao', numOuVazio(e.target.value))}/> %, atenção até{" "}
                <input className="input" type="number" disabled={ro} value={faixas.minOk ?? ''}
                       onChange={(e) => setFaixa('minOk', numOuVazio(e.target.value))}/> %, ideal até{" "}
                <input className="input" type="number" disabled={ro} value={faixas.maxOk ?? ''}
                       onChange={(e) => setFaixa('maxOk', numOuVazio(e.target.value))}/> %, acima disso = alerta.
              </div>
            </div>
          }
        />
      </Section>

      <Section title="Alertas das colunas">
        <Row
          stacked
          title="Critérios por coluna"
          desc="Define quando o ícone de cada coluna fica verde, amarelo ou vermelho. Espelha o arquivo data/config-alertas.json."
          control={
            <div style={{ width: "100%", display: "grid", gap: 12 }}>
              {alertasColunas.map((col, colIdx) => (
                <div key={col.id || colIdx} style={{ border: "1px solid var(--line)", borderRadius: 9, background: "var(--surface)", padding: "12px 14px" }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginBottom: 8 }}>
                    <div style={{ fontWeight: 600, fontSize: 13.5 }}>{col.titulo}</div>
                    <div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: 10.5, color: "var(--muted-2)", letterSpacing: ".05em" }}>
                      {col.tipo}{col.campo ? ` · ${col.campo}` : ""}
                    </div>
                  </div>
                  {col.tooltip && col.tipo !== "perfil_vs_raio" && (
                    <div style={{ fontSize: 12, color: "var(--muted)", marginBottom: 10, lineHeight: 1.45 }}>{col.tooltip}</div>
                  )}

                  {col.tipo === "faixas" && Array.isArray(col.faixas) && (() => {
                    // Layout estilo "Faixas de superconsumo": tira visual
                    // + texto narrativo. So funciona quando todas as
                    // faixas usam apenas `max` (sem `min`); se alguma
                    // tiver `min`, cai no layout antigo (mais generico).
                    const soMax = col.faixas.every(f => f.min == null);
                    if (!soMax) {
                      return (
                        <div style={{ display: "grid", gap: 6 }}>
                          {col.faixas.map((f, i) => (
                            <div key={i} style={{ display: "flex", gap: 6, alignItems: "center", flexWrap: "wrap", fontSize: 12 }}>
                              <span className="kpi-color" style={{ background: COR_ESTADO_ALERTA[f.estado] }}/>
                              <span style={{ width: 70, color: "var(--muted)" }}>{LABEL_ESTADO_ALERTA[f.estado]}</span>
                              <span className="input-suffix">de</span>
                              <input className="input input-num" style={{ width: 80 }} type="number" step="0.1" disabled={ro}
                                     value={f.min ?? ''} placeholder="—"
                                     onChange={(e) => setFaixaAlerta(colIdx, i, { min: e.target.value === '' ? null : Number(e.target.value) })}/>
                              <span className="input-suffix">a</span>
                              <input className="input input-num" style={{ width: 80 }} type="number" step="0.1" disabled={ro}
                                     value={f.max ?? ''} placeholder="—"
                                     onChange={(e) => setFaixaAlerta(colIdx, i, { max: e.target.value === '' ? null : Number(e.target.value) })}/>
                              <span className="input-suffix">{col.unidade || ''}</span>
                            </div>
                          ))}
                          <div style={{ display: "flex", gap: 6, alignItems: "center", marginTop: 4, fontSize: 12 }}>
                            <span className="kpi-color" style={{ background: COR_ESTADO_ALERTA[col.padrao] }}/>
                            <span style={{ color: "var(--muted)" }}>Padrão (sem casamento):</span>
                            <select className="select" style={{ width: 130 }} disabled={ro}
                                    value={col.padrao || 'ok'}
                                    onChange={(e) => setAlerta(colIdx, { padrao: e.target.value })}>
                              <option value="ok">Verde · ok</option>
                              <option value="atencao">Amarelo · atenção</option>
                              <option value="alerta">Vermelho · alerta</option>
                            </select>
                          </div>
                        </div>
                      );
                    }
                    // Layout uniforme com tira: cells geradas das faixas
                    // em ordem (com max crescente) + cell pro padrao.
                    const ultimaMax = col.faixas[col.faixas.length - 1]?.max;
                    return (
                      <div>
                        <div className="thresh" style={{ gridTemplateColumns: `repeat(${col.faixas.length + 1}, 1fr)` }}>
                          {col.faixas.map((f, i) => {
                            const inicio = i === 0 ? null : col.faixas[i - 1].max;
                            const range = inicio == null ? `< ${f.max}` : `${inicio} – ${f.max}`;
                            return (
                              <div className="thresh-cell" key={i}>
                                <div className="thresh-bar" style={{ background: COR_ESTADO_ALERTA[f.estado] }}/>
                                <div className="thresh-name">{NOME_ESTADO_ALERTA[f.estado] || '—'}</div>
                                <div className="thresh-range">{range}</div>
                              </div>
                            );
                          })}
                          <div className="thresh-cell">
                            <div className="thresh-bar" style={{ background: COR_ESTADO_ALERTA[col.padrao] || '#8A93A6' }}/>
                            <div className="thresh-name">{NOME_ESTADO_ALERTA[col.padrao] || '—'}</div>
                            <div className="thresh-range">{ultimaMax != null ? `> ${ultimaMax}` : '—'}</div>
                          </div>
                        </div>
                        <div className="thresh-inputs">
                          {col.faixas.map((f, i) => (
                            <React.Fragment key={i}>
                              {NOME_ESTADO_ALERTA[f.estado] || '—'} até{' '}
                              <input className="input" type="number" step="0.1" disabled={ro}
                                     value={f.max ?? ''}
                                     onChange={(e) => setFaixaAlerta(colIdx, i, { max: e.target.value === '' ? null : Number(e.target.value) })}/>
                              {col.unidade ? ` ${col.unidade}` : ''},{' '}
                            </React.Fragment>
                          ))}
                          acima disso ={' '}
                          <select className="select" style={{ width: 110 }} disabled={ro}
                                  value={col.padrao || 'ok'}
                                  onChange={(e) => setAlerta(colIdx, { padrao: e.target.value })}>
                            <option value="ok">Ideal</option>
                            <option value="atencao">Atenção</option>
                            <option value="alerta">Alerta</option>
                          </select>.
                        </div>
                      </div>
                    );
                  })()}

                  {col.tipo === "inclinacao_xy" && (
                    <div style={{ display: "flex", gap: 6, alignItems: "center", fontSize: 12 }}>
                      <span className="kpi-color" style={{ background: COR_ESTADO_ALERTA.alerta }}/>
                      <span style={{ color: "var(--muted)" }}>Vermelho quando |X| ou |Y| ≥</span>
                      <input className="input input-num" style={{ width: 80 }} type="number" step="0.1" disabled={ro}
                             value={col.limite ?? ''}
                             onChange={(e) => setAlerta(colIdx, { limite: e.target.value === '' ? null : Number(e.target.value) })}/>
                      <span className="input-suffix">{col.unidade || '°'}</span>
                    </div>
                  )}

                  {col.tipo === "perfil_vs_raio" && (
                    <div style={{ fontSize: 12, color: "var(--muted)", fontStyle: "italic" }}>
                      Sem parâmetros configuráveis: o alerta dispara automaticamente quando o perfil fica menor que o diâmetro da estaca.
                    </div>
                  )}
                </div>
              ))}
            </div>
          }
        />
      </Section>
    </>
  );
}

// Layout dos limites na tela: chave do JSON, label exibido, unidade.
const LIMITES_GRAFICOS_LAYOUT = [
  ["torque",      "Torque (MT)",       "bar"],
  ["rotacao",     "Rotação (VR)",      "rpm"],
  ["velDescida",  "Vel. descida (VS)", "m/h"],
  ["velSubida",   "Vel. subida (VA)",  "m/h"],
  ["concreto",    "Concreto (PC)",     "L/m"],
  ["inclinacaoX", "Inclinação X (IX)", "°"],
  ["inclinacaoY", "Inclinação Y (IY)", "°"],
  ["vazao",       "Vazão",             "L/min"],
];

// Cores configuraveis (8 entradas). As inclinacoes seguem o tema —
// listadas como "Linha do tema" e nao aparecem aqui.
const CORES_GRAFICOS_LAYOUT = [
  ["torque",        "Torque",       "MT"],
  ["rotacao",       "Rotação",      "VR"],
  ["velDescida",    "Vel. descida", "VS"],
  ["velSubida",     "Vel. subida",  "VA"],
  ["concreto",      "Concreto",     "PC"],
  ["vazao",         "Vazão",        "VZ"],
  ["perfilLinha",   "Perfil · linha",   "PL"],
  ["perfilHachura", "Perfil · hachura", "PH"],
];

function CfgPageCharts({ cfg, atualizarSecao, podeEditar }) {
  const ge = (cfg && cfg.graficosEstaca) || {};
  const limites = ge.limitesMaximos || {};
  const cores = ge.cores || {};
  const ro = !podeEditar;

  // Limites no formato { auto: bool, valor: number }. Helpers atualizam
  // so um campo por vez sem perder o outro.
  const limiteAtual = (chave) => {
    const l = limites[chave];
    if (l && typeof l === 'object') return { auto: !!l.auto, valor: l.valor };
    // tolerancia: configs antigas podiam guardar so o numero
    if (typeof l === 'number') return { auto: true, valor: l };
    return { auto: true, valor: '' };
  };
  const setLimiteAuto = (chave, auto) => {
    const cur = limiteAtual(chave);
    atualizarSecao('graficosEstaca', {
      limitesMaximos: { ...limites, [chave]: { auto: !!auto, valor: cur.valor } },
    });
  };
  const setLimiteValor = (chave, valor) => {
    const cur = limiteAtual(chave);
    atualizarSecao('graficosEstaca', {
      limitesMaximos: { ...limites, [chave]: { auto: cur.auto, valor: valor } },
    });
  };
  const setCor = (chave, valor) => atualizarSecao('graficosEstaca', {
    cores: { ...cores, [chave]: valor },
  });
  const numOuVazio = (s) => s === '' || s == null ? '' : Number(s);

  return (
    <>
      <Section title="Limites máximos do eixo Y">
        <Row
          stacked
          title="Modo de cada parâmetro"
          desc="Em Automático, o eixo se ajusta aos dados da estaca. Em Manual, o eixo vai sempre de 0 até o valor configurado — útil pra padronizar a escala entre estacas."
          control={
            <div style={{ width: "100%" }}>
              <div className="limits-grid" style={{ gridTemplateColumns: "1fr" }}>
                {LIMITES_GRAFICOS_LAYOUT.map(([chave, label, unit]) => {
                  const cur = limiteAtual(chave);
                  return (
                    <div className="limit-row" key={chave} style={{ gap: 12, flexWrap: 'wrap' }}>
                      <span className="limit-label" style={{ minWidth: 160 }}>{label}</span>
                      <div className="segmented" style={{ flexShrink: 0 }}>
                        <button
                          type="button"
                          className={cur.auto ? "is-active" : ""}
                          disabled={ro}
                          onClick={() => setLimiteAuto(chave, true)}
                        >Automático</button>
                        <button
                          type="button"
                          className={!cur.auto ? "is-active" : ""}
                          disabled={ro}
                          onClick={() => setLimiteAuto(chave, false)}
                        >Manual</button>
                      </div>
                      <input
                        className="input input-num"
                        type="number"
                        disabled={ro || cur.auto}
                        value={cur.valor ?? ''}
                        onChange={(e) => setLimiteValor(chave, numOuVazio(e.target.value))}
                        style={{ opacity: cur.auto ? 0.5 : 1 }}
                      />
                      <span className="limit-unit">{unit}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          }
        />
      </Section>

      <Section title="Cor das séries">
        <Row
          title="Estilo do preenchimento do perfil"
          desc="Hachura: preenchimento tracejado em duas cores (linha + hachura). Sólido: preenchimento liso usando só a cor da linha."
          control={
            <div className="segmented" style={{ flexShrink: 0 }}>
              <button
                type="button"
                className={(cores.perfilEstilo || 'hachura') === 'hachura' ? "is-active" : ""}
                disabled={ro}
                onClick={() => setCor('perfilEstilo', 'hachura')}
              >Hachura</button>
              <button
                type="button"
                className={cores.perfilEstilo === 'solido' ? "is-active" : ""}
                disabled={ro}
                onClick={() => setCor('perfilEstilo', 'solido')}
              >Sólido</button>
            </div>
          }
        />
        <Row
          stacked
          title="Paleta dos gráficos"
          desc="Cor aplicada à curva de cada parâmetro. Inclinação X e Y seguem o tema (cinza/preto) e não são configuráveis."
          control={
            <div style={{ width: "100%" }}>
              <div className="series-grid">
                {CORES_GRAFICOS_LAYOUT.map(([chave, nome, sigla]) => {
                  const valor = cores[chave] || '';
                  return (
                    <div className="series-item" key={chave}>
                      <input
                        type="color"
                        disabled={ro}
                        value={valor || '#000000'}
                        onChange={(e) => setCor(chave, e.target.value)}
                        style={{ width: 28, height: 28, padding: 0, border: '1px solid var(--line)', borderRadius: 4, background: 'transparent', cursor: ro ? 'default' : 'pointer' }}
                        title={`Cor de ${nome}`}
                      />
                      <span className="series-name">{nome}</span>
                      <input
                        type="text"
                        disabled={ro}
                        value={valor}
                        onChange={(e) => setCor(chave, e.target.value)}
                        className="input"
                        style={{ width: 110, fontFamily: "'JetBrains Mono',monospace", marginLeft: 'auto' }}
                      />
                      <span className="series-code" style={{ minWidth: 24, textAlign: 'right' }}>{sigla}</span>
                    </div>
                  );
                })}
              </div>
            </div>
          }
        />
      </Section>

    </>
  );
}

function CfgPageReport({ cfg, atualizarSecao, podeEditar }) {
  const relatorio = (cfg && cfg.relatorio) || {};
  const ro = !podeEditar;

  const setCampo = (chave, valor) => {
    atualizarSecao('relatorio', { ...relatorio, [chave]: valor });
  };

  return (
    <>
      <Section title="Identidade do relatório">
        <Row
          title="Texto do rodapé"
          desc="Aparece no rodapé de todas as páginas do PDF (1 e 4 estacas por página)."
          control={
            <input
              className="input"
              style={{ width: 320 }}
              disabled={ro}
              value={relatorio.textoRodape || ''}
              onChange={(e) => setCampo('textoRodape', e.target.value)}
              placeholder="Compugeo · www.compugeo.com.br"
            />
          }
        />
        <Row
          title="Rótulo de assinatura · Responsável"
          desc="Texto sobre a linha de assinatura interna."
          control={
            <input
              className="input"
              style={{ width: 240 }}
              disabled={ro}
              value={relatorio.labelAssinaturaResp || ''}
              onChange={(e) => setCampo('labelAssinaturaResp', e.target.value)}
              placeholder="Responsável"
            />
          }
        />
        <Row
          title="Rótulo de assinatura · Cliente"
          desc="Texto sobre a linha de assinatura do cliente."
          control={
            <input
              className="input"
              style={{ width: 240 }}
              disabled={ro}
              value={relatorio.labelAssinaturaCliente || ''}
              onChange={(e) => setCampo('labelAssinaturaCliente', e.target.value)}
              placeholder="Resp. Cliente"
            />
          }
        />
      </Section>

      <div style={{ marginTop: 10, fontSize: 12, color: 'var(--muted)' }}>
        O logo do cabeçalho do PDF é o mesmo configurado em <strong>Empresa e conta</strong>.
      </div>
    </>
  );
}

function CfgPageMonitor({ flagDirty }) {
  return (
    <>
      <Section title="Apresentação">
        <Row
          title="Cards por página"
          desc="Quantas máquinas aparecem na visualização padrão."
          control={<Segmented options={["4", "6", "8", "12"]} defaultIndex={1} onChange={flagDirty}/>}
        />
        <Row
          title="Grade do modo fullscreen"
          desc='Layout do "modo TV" exibido em telão.'
          control={<Segmented options={["3×2", "4×3", "5×3"]} defaultIndex={1} onChange={flagDirty}/>}
        />
      </Section>

      <Section title="Polling e histórico">
        <Row
          title="Intervalo de atualização"
          desc="Frequência com que cada card busca dados em tempo real."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="2000" onChange={flagDirty}/>
              <span className="input-suffix">ms</span>
            </div>
          }
        />
        <Row
          title="Tamanho do histórico"
          desc="Quantas amostras manter por máquina (afeta o sparkline)."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="32" onChange={flagDirty}/>
              <span className="input-suffix">amostras</span>
            </div>
          }
        />
      </Section>

      <Section title="Limites dos gauges">
        <Row
          title="Pressão concreto"
          desc="Faixa exibida no mostrador radial de pressão."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="0.5" step="0.1" onChange={flagDirty}/>
              <span className="input-suffix">a</span>
              <input className="input input-num" type="number" defaultValue="9.5" step="0.1" onChange={flagDirty}/>
              <span className="input-suffix">bar</span>
            </div>
          }
        />
        <Row
          title="Superconsumo (sparkline)"
          desc="Limite simétrico em torno de zero."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <span className="input-suffix">±</span>
              <input className="input input-num" type="number" defaultValue="95" onChange={flagDirty}/>
              <span className="input-suffix">%</span>
            </div>
          }
        />
        <Row
          title="Velocidade"
          desc="Faixa exibida no sparkline de velocidade."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="50" onChange={flagDirty}/>
              <span className="input-suffix">a</span>
              <input className="input input-num" type="number" defaultValue="480" onChange={flagDirty}/>
              <span className="input-suffix">m/h</span>
            </div>
          }
        />
      </Section>
    </>
  );
}

function CfgPageMap({ flagDirty }) {
  return (
    <>
      <Section title="Localização inicial">
        <Row
          title="Coordenadas"
          desc="Latitude e longitude para onde o mapa abre."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="-23.55" step="0.001" style={{ width: 110 }} onChange={flagDirty}/>
              <span className="input-suffix">,</span>
              <input className="input input-num" type="number" defaultValue="-46.62" step="0.001" style={{ width: 110 }} onChange={flagDirty}/>
            </div>
          }
        />
        <Row
          title="Zoom inicial"
          desc="De 1 (mundo) a 18 (rua)."
          control={<input className="input input-num" type="number" defaultValue="11" onChange={flagDirty}/>}
        />
      </Section>

      <Section title="Pins">
        <Row
          title="Tamanho do pin"
          desc="Diâmetro padrão e tamanho mini (zoom out)."
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="12" onChange={flagDirty}/>
              <span className="input-suffix">/</span>
              <input className="input input-num" type="number" defaultValue="10" onChange={flagDirty}/>
              <span className="input-suffix">px</span>
            </div>
          }
        />
        <Row
          title="Período da pulsação"
          desc='Tempo de uma "respiração" completa do pin ativo.'
          control={
            <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
              <input className="input input-num" type="number" defaultValue="1.4" step="0.1" onChange={flagDirty}/>
              <span className="input-suffix">s</span>
            </div>
          }
        />
        <Row
          title="Cores dos pins por status"
          desc="Espelha a paleta de status global (verde / amarelo / vermelho)."
          control={
            <div style={{ display: "flex", gap: 6 }}>
              <span className="swatch is-active" style={{ background: "#22A06B" }}/>
              <span className="swatch is-active" style={{ background: "#E08B2A" }}/>
              <span className="swatch is-active" style={{ background: "#D14343" }}/>
            </div>
          }
        />
      </Section>
    </>
  );
}

// ---------- mapa id -> componente da pagina ----------
const CFG_PAGE_COMPONENTS = {
  company:   CfgPageCompany,
  locale:    CfgPageLocale,
  dashboard: CfgPageDashboard,
  piletable: CfgPagePileTable,
  charts:    CfgPageCharts,
  report:    CfgPageReport,
  monitor:   CfgPageMonitor,
  map:       CfgPageMap,
};

// =========================================================
// View raiz das configuracoes — montada no dashboard
// =========================================================
function ConfiguracoesView({ onVoltar, tema, onSetTema, papel, notify, accent }) {
  const [activePage, setActivePage] = cfgUseState("company");
  const [savedToast, setSavedToast] = cfgUseState(false);
  const [searchQ, setSearchQ] = cfgUseState("");

  // Estado real das configuracoes. `cfgBase` e' a versao recebida do
  // servidor (referencia pra detectar dirty e suportar Descartar).
  // `cfg` e' o que o usuario esta editando — sempre derivado de cfgBase
  // ate alguem alterar algo. `cfgDefaults` espelha as configs de fabrica
  // (vem do backend) — usado pelo botao "Restaurar padrao".
  const [cfg, setCfg] = cfgUseState(null);
  const [cfgBase, setCfgBase] = cfgUseState(null);
  const [cfgDefaults, setCfgDefaults] = cfgUseState(null);
  const [carregando, setCarregando] = cfgUseState(true);
  const [erroCarga, setErroCarga] = cfgUseState(null);
  const [salvando, setSalvando] = cfgUseState(false);

  // Hoje qualquer usuario logado pode salvar (preferencias pessoais vao
  // pra `preferencias_usuario`). A secao Empresa continua restrita: o
  // proprio backend ignora o campo `empresa` quando o user nao e' admin.
  // O front passa ambos os flags pras paginas pra desabilitar os controles
  // de empresa em UI tambem (UX consistente com o backend).
  const podeEditar = true;
  const podeEditarEmpresa = papel === 'admin_sistema' || papel === 'admin_cliente';

  // dirty = cfg difere da base. Comparacao por JSON evita falso negativo
  // de mutacao in-place (mas nada deve mutar — sempre setCfg novo).
  const dirty = cfg && cfgBase && JSON.stringify(cfg) !== JSON.stringify(cfgBase);
  // Diferente do padrao = base salva no banco difere dos defaults de
  // fabrica. Quando true, exibimos a save-bar mesmo sem dirty pra dar
  // acesso ao botao "Restaurar padrao".
  const difereDoPadrao = cfgBase && cfgDefaults && JSON.stringify(cfgBase) !== JSON.stringify(cfgDefaults);

  // Carrega configuracoes ao montar.
  cfgUseEffect(() => {
    let vivo = true;
    fetch('/api/configuracoes', { credentials: 'include' })
      .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
      .then(j => {
        if (!vivo) return;
        const dados = j.dados || {};
        setCfg(dados);
        setCfgBase(dados);
        if (j.defaults) setCfgDefaults(j.defaults);
        setCarregando(false);
      })
      .catch(err => {
        if (!vivo) return;
        console.error('Erro ao carregar configuracoes:', err);
        setErroCarga(err.message || 'Falha ao carregar configuracoes');
        setCarregando(false);
      });
    return () => { vivo = false; };
  }, []);

  const handleSave = async () => {
    if (!cfg || salvando) return;
    setSalvando(true);
    try {
      const r = await fetch('/api/configuracoes', {
        method: 'PUT',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ dados: cfg }),
      });
      if (!r.ok) {
        const txt = await r.text().catch(() => '');
        throw new Error(`HTTP ${r.status}${txt ? ' — ' + txt : ''}`);
      }
      setCfgBase(cfg);
      setSavedToast(true);
      setTimeout(() => setSavedToast(false), 2000);
    } catch (err) {
      console.error('Erro ao salvar:', err);
      alert('Falha ao salvar: ' + (err.message || err));
    } finally {
      setSalvando(false);
    }
  };

  const handleDiscard = () => {
    if (cfgBase) setCfg(cfgBase);
  };

  // Restaurar default: copia os defaults para o cfg. Nao salva
  // automaticamente — o usuario revisa e decide. Confirm() pra evitar
  // clique acidental, ja que isso afeta a tela inteira.
  const handleRestaurarPadrao = () => {
    if (!cfgDefaults || !podeEditar) return;
    if (!window.confirm('Restaurar todas as configurações para os valores default?\n\nAs alterações vão aparecer marcadas como "não salvas" — você ainda precisa clicar em Salvar para aplicar.')) return;
    // Deep clone pra que mudancas posteriores no cfg nao corrompam o
    // cfgDefaults guardado em estado.
    setCfg(JSON.parse(JSON.stringify(cfgDefaults)));
  };

  // Helper passado pras paginas: atualiza uma secao do cfg de forma
  // imutavel. `secao` e' uma chave top-level (ex: "tabelaEstacas") e
  // `patch` e' um objeto com os campos a sobrescrever (merge raso).
  const atualizarSecao = cfgUseCallback((secao, patch) => {
    setCfg(c => ({ ...(c || {}), [secao]: { ...((c && c[secao]) || {}), ...patch } }));
  }, []);

  // Agrupa paginas por grupo, respeitando filtro da busca
  const grupos = cfgUseMemo(() => {
    const q = searchQ.trim().toLowerCase();
    const filtra = (p) => !q || p.title.toLowerCase().includes(q) || p.sub.toLowerCase().includes(q);
    const out = [];
    CFG_PAGES.forEach((p) => {
      if (!filtra(p)) return;
      let g = out.find((x) => x.nome === p.group);
      if (!g) { g = { nome: p.group, itens: [] }; out.push(g); }
      g.itens.push(p);
    });
    return out;
  }, [searchQ]);

  const pagina = CFG_PAGES.find((p) => p.id === activePage) || CFG_PAGES[0];
  const PageComp = CFG_PAGE_COMPONENTS[pagina.id];

  return (
    <div className="settings-root">
      {/* ---------- Sidebar ---------- */}
      <aside className="set-side">
        <div className="set-side-head">
          <button className="back-btn" aria-label="Voltar" onClick={() => onVoltar && onVoltar()}>
            <CfgIcon.back/>
          </button>
          <div className="set-side-title">Configurações</div>
        </div>

        <div className="search-box">
          <CfgIcon.search/>
          <input
            type="text"
            placeholder="Buscar configuração…"
            value={searchQ}
            onChange={(e) => setSearchQ(e.target.value)}
          />
          <kbd>⌘K</kbd>
        </div>

        {grupos.map((g) => (
          <React.Fragment key={g.nome}>
            <div className="set-group-label">{g.nome}</div>
            {g.itens.map((p) => {
              const Icon = CfgIcon[p.icon];
              return (
                <button
                  key={p.id}
                  className={`set-nav ${p.id === activePage ? "is-active" : ""}`}
                  onClick={() => setActivePage(p.id)}
                >
                  <div className="set-nav-icon">{Icon ? <Icon/> : null}</div>
                  <div className="set-nav-text">
                    <div className="set-nav-title">{p.title}</div>
                    <div className="set-nav-sub">{p.sub}</div>
                  </div>
                </button>
              );
            })}
          </React.Fragment>
        ))}
      </aside>

      {/* ---------- Conteudo ---------- */}
      <div className="set-main">
        <PageHead eyebrow={pagina.eyebrow} title={pagina.title} desc={pagina.desc}/>
        {carregando ? (
          <div style={{ padding: 24, color: 'var(--muted)' }}>Carregando configuracoes…</div>
        ) : erroCarga ? (
          <div style={{ padding: 24, color: 'var(--alert)' }}>Erro: {erroCarga}</div>
        ) : PageComp ? (
          <PageComp
            cfg={cfg}
            atualizarSecao={atualizarSecao}
            podeEditar={podeEditar}
            podeEditarEmpresa={podeEditarEmpresa}
            flagDirty={() => {}}
            papel={papel}
            notify={notify}
            accent={accent}
          />
        ) : null}
      </div>

      {/* ---------- Save bar ---------- */}
      {/* Visivel quando:
            - dirty (alteracoes nao salvas) OU
            - config no banco difere do padrao de fabrica (pra dar
              acesso ao botao "Restaurar padrao" mesmo sem mudancas).
          O botao Restaurar padrao aparece sempre que podeEditar; os
          botoes Descartar/Salvar so quando dirty. */}
      <div className={`save-bar ${(dirty || difereDoPadrao) && podeEditar ? "is-visible" : ""}`}>
        <div className="save-bar-msg">
          <span className="save-bar-dot"/>
          {salvando
            ? 'Salvando…'
            : dirty
              ? 'Você tem alterações não salvas'
              : 'Configuração atual difere do default'}
        </div>
        {podeEditar && cfgDefaults && (
          <button className="btn btn-ghost" onClick={handleRestaurarPadrao} disabled={salvando}>
            Restaurar default
          </button>
        )}
        {dirty && (
          <button className="btn btn-ghost" onClick={handleDiscard} disabled={salvando}>Descartar</button>
        )}
        {dirty && (
          <button className="btn btn-primary" onClick={handleSave} disabled={salvando || !podeEditar}>
            {salvando ? 'Salvando…' : 'Salvar alterações'}
          </button>
        )}
      </div>

      {/* ---------- Toast ---------- */}
      <div className={`toast ${savedToast ? "is-visible" : ""}`}>
        <CfgIcon.check/>
        Configurações salvas
      </div>
    </div>
  );
}

// disponibiliza globalmente pra login-dash.jsx
window.ConfiguracoesView = ConfiguracoesView;
