<?php /** @var string $title */ /** @var string $entity */ ?>
<div class="card" style="margin-top:0;">
  <div class="header" style="margin-bottom:0.5rem;">
    <h1 class="title" style="margin:0;"><?= htmlspecialchars($title ?? 'Cadastros') ?></h1>
    <p class="muted" style="margin-top:0.25rem;">Gerencie registros de <?= htmlspecialchars($entity) ?> com busca, filtros e paginação.</p>
  </div>

  <section class="card">
    <div class="form-row">
      <div class="form-group">
        <label>Busca</label>
        <input id="q" type="text" placeholder="Digite para buscar" />
      </div>
      <div class="form-group">
        <label>Ordenar por</label>
        <select id="sort">
          <option value="id">ID</option>
          <option value="nome">Nome</option>
          <option value="email">E-mail</option>
          <option value="created_at">Criado em</option>
        </select>
      </div>
      <div class="form-group">
        <label>Direção</label>
        <select id="dir">
          <option value="DESC">Desc</option>
          <option value="ASC">Asc</option>
        </select>
      </div>
    </div>
    <div class="header" style="margin-top:.5rem;">
      <div></div>
      <div style="display:flex; gap:.5rem; flex-wrap: wrap;">
        <button id="btnBuscar" class="btn-outline" title="Executar busca">🔍 Buscar</button>
        <button id="btnLimpar" class="btn-outline" title="Limpar busca">🧹 Limpar</button>
        <button id="btnNovo" class="btn" title="Criar novo registro">➕ Novo</button>
        <button id="btnExport" class="btn-outline" title="Exportar listagem em CSV/XLSX">📄 Exportar</button>
        <button id="btnImport" class="btn-outline" title="Importar via arquivo">📥 Importar</button>
        <button id="btnPdf" class="btn-outline" title="Imprimir PDF com filtros">🖨️ PDF</button>
        <button id="btnBulk" class="btn" title="Editar em massa registros selecionados">🛠️ Edição em massa</button>
      </div>
    </div>
  </section>

  <section class="card">
    <div class="table-responsive">
    <table class="table">
      <thead>
        <tr id="thead"></tr>
      </thead>
      <tbody id="rows"><tr><td colspan="6" class="muted">Carregando…</td></tr></tbody>
    </table>
    </div>
    <div class="header" style="margin-top:.5rem;">
      <span id="pageInfo" class="muted"></span>
      <div style="display:flex; gap:.5rem;">
        <button id="prev" class="btn-outline">Anterior</button>
        <button id="next" class="btn">Próxima</button>
      </div>
    </div>
  </section>

  <dialog id="dlgForm">
    <form method="dialog" class="card">
      <div class="header" style="display:flex; align-items:center; justify-content:space-between;">
        <h3 id="dlgTitle" class="title" style="margin:0;">Novo</h3>
        <div style="display:flex; gap:.25rem;">
          <button type="button" id="dlgMin" class="btn-outline" title="Minimizar">—</button>
          <button type="button" id="dlgMax" class="btn-outline" title="Maximizar">⬜</button>
          <button type="button" id="dlgClose" class="btn-outline" title="Fechar">✖</button>
        </div>
      </div>
      <div id="formFields" class="form-row cols-2"></div>
      <div class="header" style="margin-top:.5rem;">
        <div></div>
        <div style="display:flex; gap:.5rem;">
          <button id="btnSalvar" class="btn" type="submit">Salvar</button>
          <button id="btnCancelar" class="btn-outline" type="button">Cancelar</button>
        </div>
      </div>
      <div id="formAlert" class="alert alert-error" role="alert" style="display:none; margin-top:.25rem;"></div>
    </form>
  </dialog>

  <dialog id="dlgImport">
    <form method="dialog" class="card">
      <h3 class="title">Importar registros</h3>
      <p class="muted">Baixe o modelo, preencha e envie. Arquivos suportados: CSV, XLSX. Limite: 2MB.</p>
      <div style="display:flex; gap:.5rem; align-items:center;">
        <a id="tplCsv" class="btn-outline" href="#" download>⬇️ Modelo CSV</a>
        <a id="tplXlsx" class="btn-outline" href="#" download>⬇️ Modelo XLSX</a>
      </div>
      <div class="form-group" style="margin-top:.5rem;">
        <label for="fileImport">Arquivo</label>
        <input id="fileImport" type="file" accept=".csv, .xlsx" />
      </div>
      <div class="header" style="margin-top:.5rem;">
        <div></div>
        <div style="display:flex; gap:.5rem;">
          <button id="btnDoImport" class="btn" type="submit">Importar</button>
          <button id="btnCancelImport" class="btn-outline" type="button">Cancelar</button>
        </div>
      </div>
      <div id="importAlert" class="alert alert-error" role="alert" style="display:none; margin-top:.25rem;"></div>
    </form>
  </dialog>

  <dialog id="dlgBulk">
    <form method="dialog" class="card">
      <h3 class="title">Edição em massa</h3>
      <p class="muted">Aplicar alterações nos registros selecionados. Conflitos serão mostrados.</p>
      <div id="bulkFields" class="form-row cols-2"></div>
      <div class="header" style="margin-top:.5rem;">
        <div id="bulkProgress" class="muted"></div>
        <div style="display:flex; gap:.5rem;">
          <button id="btnDoBulk" class="btn" type="submit">Aplicar</button>
          <button id="btnCancelBulk" class="btn-outline" type="button">Cancelar</button>
        </div>
      </div>
      <div id="bulkAlert" class="alert alert-error" role="alert" style="display:none; margin-top:.25rem;"></div>
    </form>
  </dialog>

  <dialog id="dlgView">
    <form method="dialog" class="card">
      <h3 class="title">Detalhes</h3>
      <div id="viewContent" class="form-row cols-2"></div>
      <div class="header" style="margin-top:.5rem;">
        <div></div>
        <div style="display:flex; gap:.5rem; align-items:center;">
          <button id="viewMin" class="btn-outline" type="button" title="Minimizar">➖</button>
          <button id="viewMax" class="btn-outline" type="button" title="Maximizar">🗖</button>
          <button id="viewClose" class="btn" type="submit" title="Fechar">✖️</button>
        </div>
      </div>
    </form>
  </dialog>

  <dialog id="dlgConfirm">
    <form method="dialog" class="card">
      <h3 class="title" id="confirmTitle">Confirmar ação</h3>
      <p id="confirmMsg" class="muted" style="margin:.25rem 0 .5rem 0;">Tem certeza?</p>
      <div class="header" style="margin-top:.5rem;">
        <div></div>
        <div style="display:flex; gap:.5rem;">
          <button id="btnConfirmYes" class="btn" type="submit">Confirmar</button>
          <button id="btnConfirmNo" class="btn-outline" type="button">Cancelar</button>
        </div>
      </div>
    </form>
  </dialog>
</div>

<script>
const entity = <?= json_encode($entity) ?>;
let baseUrl = '/api/cadastros/' + entity;
if (entity === 'usuarios') baseUrl = '/api/auth/users';
let page = 1; let limit = 20; let total = 0;
let currentCols = []; let currentRows = [];
const selectedIds = new Set();

function escapeHtml(s){ return String(s||'').replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
async function api(url, options){
  // Usar helper global que já injeta Authorization e x-tenant-id
  if (typeof window.apiRequest === 'function') {
    return window.apiRequest(url, options || {});
  }
  // Fallback: acrescenta Authorization e x-tenant-id manualmente
  const opts = { ...(options||{}), credentials:'same-origin' };
  const isForm = opts.body && (opts.body instanceof FormData);
  const headers = { ...(opts.headers||{}) };
  if (!isForm) headers['Content-Type'] = headers['Content-Type'] || 'application/json';
  try {
    const ls = localStorage.getItem('auth_token');
    const m = document.cookie.match(/(?:^|; )auth_token=([^;]+)/);
    const token = ls || (m ? decodeURIComponent(m[1]) : null);
    if (token && !headers['Authorization']) headers['Authorization'] = 'Bearer ' + token;
  } catch(_){}
  if (!headers['x-tenant-id']) {
    const mt = document.cookie.match(/(?:^|; )x-tenant-id=([^;]+)/);
    headers['x-tenant-id'] = mt ? decodeURIComponent(mt[1]) : 'default';
  }
  opts.headers = headers;
  return fetch(url, opts);
}

function getEntityConfig(ent){
  const cfg = {
    empresas: {
      fields: [
        {id:'nome', label:'Nome', type:'text', required:true},
        {id:'razao_social', label:'Razão social', type:'text'},
        {id:'cnpj', label:'CNPJ', type:'text', attrs:{inputmode:'numeric', maxlength:18}},
        {id:'ie', label:'Inscrição Estadual', type:'text'},
        {id:'im', label:'Inscrição Municipal', type:'text'},
        {id:'email', label:'E-mail', type:'email'},
        {id:'telefone', label:'Telefone', type:'text'},
        {id:'telefone_alt', label:'Telefone alternativo', type:'text'},
        {id:'whatsapp', label:'WhatsApp', type:'text'},
        {id:'site_url', label:'Site', type:'url'},
        {id:'classificacao', label:'Classificação', type:'select', options:[
          {value:'', text:'—'},
          {value:'holding', text:'Holding'},
          {value:'matriz', text:'Matriz'},
          {value:'filial', text:'Filial'},
          {value:'parceira', text:'Parceira'},
          {value:'cliente', text:'Cliente'},
          {value:'fornecedor', text:'Fornecedor'}
        ]},
        {id:'historico_interacoes', label:'Histórico de interações', type:'textarea', attrs:{rows:'4', placeholder:'Anotações e contatos relevantes'}},
        {id:'logo_file', label:'Logo (arquivo)', type:'file', attrs:{accept:'image/*'}}
      ]
    },
    fornecedores: {
      fields: [
        {id:'empresa_id', label:'Empresa', type:'select', required:true, options:'empresas'},
        {id:'nome', label:'Nome', type:'text', required:true},
        {id:'tipo_pessoa', label:'Tipo de pessoa', type:'select', options:[{value:'PF', text:'PF'},{value:'PJ', text:'PJ'}]},
        {id:'cpf', label:'CPF', type:'text', attrs:{inputmode:'numeric', maxlength:14}},
        {id:'cnpj', label:'CNPJ', type:'text', attrs:{inputmode:'numeric', maxlength:18}},
        {id:'ie', label:'Inscrição Estadual', type:'text'},
        {id:'email', label:'E-mail', type:'email'},
        {id:'telefone', label:'Telefone', type:'text'},
        {id:'contato', label:'Contato', type:'text'},
        {id:'site_url', label:'Site', type:'url'}
      ]
    },
    transportadoras: {
      fields: [
        {id:'empresa_id', label:'Empresa', type:'select', required:true, options:'empresas'},
        {id:'nome', label:'Nome', type:'text', required:true},
        {id:'cnpj', label:'CNPJ', type:'text'},
        {id:'ie', label:'Inscrição Estadual', type:'text'},
        {id:'email', label:'E-mail', type:'email'},
        {id:'telefone', label:'Telefone', type:'text'},
        {id:'capacidade', label:'Capacidade (kg)', type:'number'}
      ]
    },
    usuarios: {
      fields: [
        {id:'name', label:'Nome', type:'text', required:true},
        {id:'email', label:'E-mail', type:'email', required:true},
        {id:'password', label:'Senha', type:'password'},
        {id:'roles', label:'Papéis', type:'multiselect', required:true, options:'roles'},
        {id:'enabled', label:'Ativo', type:'checkbox'}
      ]
    },
    vendedores: {
      fields: [
        {id:'empresa_id', label:'Empresa', type:'select', required:true, options:'empresas'},
        {id:'cliente', label:'Cliente (link)', type:'text', attrs:{placeholder:'Digite para buscar cliente'}},
        {id:'cliente_id', label:'Cliente ID', type:'text', attrs:{'data-hidden':'true'}},
        {id:'user', label:'Usuário (link)', type:'text', attrs:{placeholder:'Digite para buscar usuário'}},
        {id:'user_id', label:'Usuário ID', type:'text', attrs:{'data-hidden':'true'}},
        {id:'nome', label:'Nome', type:'text', required:true},
        {id:'email', label:'E-mail', type:'email'},
        {id:'telefone', label:'Telefone', type:'text'},
        {id:'comissao_percent', label:'Comissão (%)', type:'number', attrs:{step:'0.01', min:'0'}}
      ]
    },
    tecnicos: {
      fields: [
        {id:'empresa_id', label:'Empresa', type:'select', required:true, options:'empresas'},
        {id:'nome', label:'Nome', type:'text', required:true},
        {id:'email', label:'E-mail', type:'email'},
        {id:'telefone', label:'Telefone', type:'text'},
        {id:'especialidade', label:'Especialidade', type:'text'}
      ]
    }
  };
  return cfg[ent] || { fields: [{id:'nome', label:'Nome', type:'text', required:true},{id:'email', label:'E-mail', type:'email'},{id:'telefone', label:'Telefone', type:'text'}] };
}

async function loadEmpresasOptions(){
  try {
    const resp = await api('/api/cadastros/empresas?limit=200&sort=nome&dir=ASC');
    if (!resp.ok) return [];
    const json = await resp.json();
    return (json.data||[]).map(r => ({ value: String(r.id), text: r.nome || r.razao_social || ('Empresa #' + r.id) }));
  } catch(_) { return []; }
}

async function loadRolesOptions(){
  try {
    const resp = await api('/api/auth/roles');
    if (!resp.ok) return [];
    const json = await resp.json();
    return (json.data||[]).map(r => ({ value: String(r.name), text: r.name }));
  } catch(_) { return []; }
}

async function renderForm(mode, data){
  const cont = document.getElementById('formFields');
  cont.innerHTML = '';
  const cfg = getEntityConfig(entity);
  let empresasOpts = [];
  let rolesOpts = [];
  if (cfg.fields.some(f => f.options === 'empresas')) empresasOpts = await loadEmpresasOptions();
  if (cfg.fields.some(f => f.options === 'roles')) rolesOpts = await loadRolesOptions();
  cfg.fields.forEach(f => {
    const group = document.createElement('div'); group.className = 'form-group';
    const label = document.createElement('label'); label.textContent = f.label; label.setAttribute('for', 'f_'+f.id);
    group.appendChild(label);
    let input;
    if (f.type === 'select') {
      input = document.createElement('select');
      if (Array.isArray(f.options)) {
        f.options.forEach(opt => { const o = document.createElement('option'); o.value = opt.value; o.textContent = opt.text; input.appendChild(o); });
      } else if (f.options === 'empresas') {
        // Filtro rápido para empresas
        const filter = document.createElement('input');
        filter.type = 'text';
        filter.placeholder = 'Filtrar empresas';
        filter.className = 'input-filter';
        filter.style.marginBottom = '.25rem';
        group.appendChild(filter);
        const o0 = document.createElement('option'); o0.value=''; o0.textContent='Selecione'; input.appendChild(o0);
        empresasOpts.forEach(opt => { const o = document.createElement('option'); o.value = opt.value; o.textContent = opt.text; input.appendChild(o); });
        filter.addEventListener('input', ()=>{
          const term = (filter.value||'').toLowerCase();
          const sel = input; const cur = sel.value;
          sel.innerHTML = '';
          const o0b = document.createElement('option'); o0b.value=''; o0b.textContent='Selecione'; sel.appendChild(o0b);
          empresasOpts.filter(opt => opt.text.toLowerCase().includes(term)).forEach(opt => { const o = document.createElement('option'); o.value = opt.value; o.textContent = opt.text; sel.appendChild(o); });
          // Restaura seleção se ainda existir
          const has = Array.from(sel.options).some(o => o.value === cur);
          sel.value = has ? cur : '';
        });
      }
    } else if (f.type === 'multiselect') {
      // Select múltiplo com filtro
      input = document.createElement('select'); input.multiple = true; input.size = 6;
      if (f.options === 'roles') {
        const filter = document.createElement('input');
        filter.type = 'text'; filter.placeholder = 'Filtrar papéis'; filter.className = 'input-filter'; filter.style.marginBottom = '.25rem';
        group.appendChild(filter);
        rolesOpts.forEach(opt => { const o = document.createElement('option'); o.value = opt.value; o.textContent = opt.text; input.appendChild(o); });
        filter.addEventListener('input', ()=>{
          const term = (filter.value||'').toLowerCase();
          const sel = input; const curVals = Array.from(sel.selectedOptions).map(o=>o.value);
          sel.innerHTML = '';
          rolesOpts.filter(opt => opt.text.toLowerCase().includes(term)).forEach(opt => { const o = document.createElement('option'); o.value = opt.value; o.textContent = opt.text; sel.appendChild(o); });
          // Restaura seleção
          Array.from(sel.options).forEach(o => { if (curVals.includes(o.value)) o.selected = true; });
        });
        // Aviso se nenhum papel existir
        if (!rolesOpts.length) {
          const note = document.createElement('div'); note.className='muted'; note.textContent='Nenhum papel encontrado. Cadastre em Configurações → Permissões (RBAC).';
          note.style.marginTop = '.25rem'; group.appendChild(note);
        }
      }
    } else if (f.type === 'checkbox') {
      input = document.createElement('input'); input.type = 'checkbox';
    } else if (f.type === 'textarea') {
      input = document.createElement('textarea');
      if (f.attrs?.rows) input.rows = parseInt(f.attrs.rows, 10) || 3;
    } else if (f.type === 'file') {
      input = document.createElement('input'); input.type = 'file';
    } else {
      input = document.createElement('input'); input.type = f.type || 'text';
    }
    input.id = 'f_'+f.id;
    if (f.required) input.required = true;
    // Senha: obrigatória apenas na criação
    if (f.id === 'password') { input.required = (mode === 'create'); }
    if (f.attrs) Object.entries(f.attrs).forEach(([k,v]) => input.setAttribute(k, v));
    // Ocultar grupos marcados como hidden
    if (f.attrs && (f.attrs.hidden === true || f.attrs['data-hidden'] === 'true')) { group.style.display = 'none'; }
    if (data && f.id in data) {
      if (f.type === 'checkbox') input.checked = !!data[f.id];
      else if (f.type === 'multiselect' && Array.isArray(data[f.id])) {
        const vals = data[f.id].map(String);
        Array.from(input.options).forEach(o => { o.selected = vals.includes(o.value); });
      } else input.value = String(data[f.id] ?? '');
    }
    // Máscaras básicas
    if (f.id === 'cpf') input.addEventListener('input', ()=> { input.value = formatCpf(input.value); });
    if (f.id === 'cnpj') {
      input.addEventListener('input', ()=> {
        input.value = formatCnpj(input.value);
        const d = digits(input.value);
        if (d.length === 14) { lookupCnpjAndFill(d); }
      });
      input.addEventListener('blur', ()=> {
        const d = digits(input.value);
        if (d.length === 14) { lookupCnpjAndFill(d); }
      });
    }
    if (f.id === 'telefone' || f.id === 'phone' || f.id === 'telefone_alt' || f.id === 'whatsapp') input.addEventListener('input', ()=> { input.value = formatPhone(input.value); });
    if (f.id === 'logo_file') {
      const preview = document.createElement('img');
      preview.alt = 'Preview do logo'; preview.style.maxHeight = '48px'; preview.style.marginTop = '.25rem'; preview.style.display = 'none';
      group.appendChild(preview);
      input.addEventListener('change', ()=>{
        const file = input.files && input.files[0];
        if (!file) { preview.style.display='none'; preview.removeAttribute('src'); return; }
        const reader = new FileReader(); reader.onload = (e)=>{ preview.src = e.target.result; preview.style.display=''; }; reader.readAsDataURL(file);
      });
    }
    // Datalist simples para sugestões
    if (Array.isArray(f.datalist) && f.datalist.length) {
      const dlId = 'dl_'+f.id;
      input.setAttribute('list', dlId);
      const dl = document.createElement('datalist'); dl.id = dlId;
      f.datalist.forEach(v=>{ const o = document.createElement('option'); o.value = v; dl.appendChild(o); });
      group.appendChild(dl);
    }
    // Automatizações leves
    if (f.id === 'nome') {
      input.addEventListener('blur', ()=>{
        const rs = document.getElementById('f_razao_social'); if (rs && !rs.value.trim()) rs.value = input.value;
      });
    }
    if (f.id === 'telefone') {
      input.addEventListener('blur', ()=>{
        const wa = document.getElementById('f_whatsapp'); if (wa && !wa.value.trim()) wa.value = input.value;
      });
    }
    // Limpa erro ao digitar
    input.addEventListener('input', ()=> {
      const errEl = document.getElementById('err_'+f.id);
      if (errEl) { errEl.textContent = ''; errEl.style.display = 'none'; }
  const formAlert = document.getElementById('formAlert');
  if (formAlert) { formAlert.style.display = 'none'; formAlert.textContent=''; }
    });
    group.appendChild(input);
    const err = document.createElement('small'); err.id = 'err_'+f.id; err.className = 'error-text'; err.style.color = '#c00'; err.style.display = 'none';
    group.appendChild(err);
    cont.appendChild(group);
  });
  // Autocomplete de clientes para novo usuário
  if (entity === 'usuarios' && mode === 'create') {
    setupUserNameAutocomplete();
  }
  // Autocomplete de vínculos para vendedores
  if (entity === 'vendedores') {
    setupVendedorLinkAutocomplete();
  }
}

function collectPayload(){
  const cfg = getEntityConfig(entity);
  const payload = {};
  cfg.fields.forEach(f => {
    const el = document.getElementById('f_'+f.id);
    if (!el) return;
    if (f.type === 'checkbox') payload[f.id] = !!el.checked;
    else if (f.type === 'multiselect') payload[f.id] = Array.from(el.selectedOptions).map(o=>o.value);
    else payload[f.id] = el.value.trim();
  });
  // Remover campos auxiliares e incluir IDs ocultos para vendedores
  if (entity === 'vendedores') {
    delete payload['cliente']; delete payload['user'];
    const cIdEl = document.getElementById('f_cliente_id'); if (cIdEl) payload['cliente_id'] = cIdEl.value.trim();
    const uIdEl = document.getElementById('f_user_id'); if (uIdEl) payload['user_id'] = uIdEl.value.trim();
  }
  return payload;
}

function validatePayloadDetailed(p){
  const errors = { global: [], fields: {} };
  const addField = (id, msg) => { errors.fields[id] = msg; errors.global.push(msg); };
  if (entity === 'usuarios') {
    if (!p.name) addField('name','Nome é obrigatório.');
    if (!p.email) addField('email','E-mail é obrigatório.');
    const isCreate = !document.getElementById('dlgForm').getAttribute('data-id');
    if (isCreate && !p.password) addField('password','Senha é obrigatória na criação.');
    if (!Array.isArray(p.roles) || p.roles.length === 0) addField('roles','Selecione ao menos um papel.');
  } else {
    if (!p.nome) addField('nome','Nome é obrigatório.');
  }
  if (entity === 'fornecedores') {
    if (!p.empresa_id) addField('empresa_id','Selecione a empresa.');
    if (p.tipo_pessoa === 'PF') { if (!p.cpf) addField('cpf','CPF é obrigatório para PF.'); }
    if (p.tipo_pessoa === 'PJ') { if (!p.cnpj) addField('cnpj','CNPJ é obrigatório para PJ.'); }
  }
  if (entity === 'vendedores') {
    if (!p.empresa_id) addField('empresa_id','Selecione a empresa.');
    const typedCli = (document.getElementById('f_cliente')?.value || '').trim();
    if (typedCli && (!p.cliente_id || Number(p.cliente_id) <= 0)) addField('cliente','Selecione um cliente da lista.');
    const typedUser = (document.getElementById('f_user')?.value || '').trim();
    if (typedUser && (!p.user_id || Number(p.user_id) <= 0)) addField('user','Selecione um usuário da lista.');
    if (p.comissao_percent !== undefined && String(p.comissao_percent).trim() !== '') {
      const cp = Number(p.comissao_percent);
      if (!Number.isFinite(cp) || cp < 0 || cp > 100) addField('comissao_percent','Comissão deve estar entre 0 e 100.');
    }
  }
  if (entity === 'empresas' && p.cnpj) {
    const d = p.cnpj.replace(/\D/g,''); if (d.length !== 14) addField('cnpj','CNPJ deve conter 14 dígitos.');
  }
  if (p.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(p.email)) addField('email','E-mail inválido.');
  return errors;
}
function getListColumns(ent){
  const base = [{id:'id', label:'ID'}];
  const cfg = {
    empresas: [...base, {id:'logo_url', label:'Logo'}, {id:'nome', label:'Nome'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'classificacao', label:'Classificação'}, {id:'created_at', label:'Criado'}],
    fornecedores: [...base, {id:'empresa_id', label:'Empresa'}, {id:'nome', label:'Nome'}, {id:'cnpj', label:'CNPJ'}, {id:'cpf', label:'CPF'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'created_at', label:'Criado'}],
    transportadoras: [...base, {id:'empresa_id', label:'Empresa'}, {id:'nome', label:'Nome'}, {id:'cnpj', label:'CNPJ'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'capacidade', label:'Capacidade'}, {id:'created_at', label:'Criado'}],
    usuarios: [...base, {id:'name', label:'Nome'}, {id:'email', label:'E-mail'}, {id:'roles', label:'Papéis'}, {id:'enabled', label:'Ativo'}, {id:'created_at', label:'Criado'}],
    vendedores: [...base, {id:'empresa_id', label:'Empresa'}, {id:'cliente_id', label:'Cliente'}, {id:'user_id', label:'Usuário'}, {id:'nome', label:'Nome'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'comissao_percent', label:'Comissão %'}, {id:'created_at', label:'Criado'}],
    tecnicos: [...base, {id:'empresa_id', label:'Empresa'}, {id:'nome', label:'Nome'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'especialidade', label:'Especialidade'}, {id:'created_at', label:'Criado'}]
  };
  return cfg[ent] || [...base, {id:'nome', label:'Nome'}, {id:'email', label:'E-mail'}, {id:'telefone', label:'Telefone'}, {id:'created_at', label:'Criado'}];
}

function renderThead(cols){
  const thead = document.getElementById('thead');
  const sel = `<th style="width:2rem;"><input id="selAll" type="checkbox" title="Selecionar todos" /></th>`;
  const ths = sel + cols.map(c => `<th>${escapeHtml(c.label)}</th>`).join('') + '<th style="min-width:10rem;">Ações</th>';
  thead.innerHTML = ths;
  const sa = document.getElementById('selAll');
  if (sa) sa.addEventListener('change', ()=>{
    const all = sa.checked;
    Array.from(document.querySelectorAll('#rows input[type=checkbox][data-id]')).forEach(ch => {
      ch.checked = all; const id = Number(ch.getAttribute('data-id')); if (all) selectedIds.add(id); else selectedIds.delete(id);
    });
  });
}

function fmtRow(r, cols){
  const tds = cols.map(c => {
    const key = c.id; let val = r[key];
    if (key === 'nome') val = r.nome || r.name || '-';
    if (key === 'enabled') val = r.enabled ? 'Sim' : 'Não';
    if (key === 'created_at') val = r.created_at || '-';
    if (key === 'empresa_id') val = r.empresa_nome || r.empresa_id || '-';
    if (key === 'telefone' || key === 'phone') val = r.telefone || r.phone || '-';
    if (key === 'roles') {
      const roles = Array.isArray(r.roles) ? r.roles : (typeof r.roles === 'string' ? r.roles.split(',').map(s=>s.trim()).filter(Boolean) : []);
      return `<td>${roles.length ? escapeHtml(roles.join(', ')) : '-'}</td>`;
    }
    if (key === 'logo_url') {
      const url = String(val||'').trim();
      if (!url) return '<td>-</td>';
      return `<td><img src="${escapeHtml(url)}" alt="Logo" style="height:32px; max-width:64px; object-fit:contain;"/></td>`;
    }
    if (val === undefined || val === null || val === '') val = '-';
    return `<td>${escapeHtml(String(val))}</td>`;
  }).join('');
  const sel = `<td style="text-align:center;"><input type="checkbox" data-id="${r.id}" ${selectedIds.has(Number(r.id))?'checked':''} title="Selecionar" /></td>`;
  const acts = `<td>
    <button data-act="view" title="Visualizar" data-id="${r.id}">👁️</button>
    <button data-act="edit" title="Editar" data-id="${r.id}">✏️</button>
    <button data-act="del" title="Excluir" data-id="${r.id}">🗑️</button>
  </td>`;
  return `<tr>${sel}${tds}${acts}</tr>`;
}
async function load(){
  const q = document.getElementById('q').value||'';
  let sort = document.getElementById('sort').value||'id';
  const dir = document.getElementById('dir').value||'DESC';
  if (entity === 'usuarios' && sort === 'nome') sort = 'name';
  const url = `${baseUrl}?page=${page}&limit=${limit}&sort=${encodeURIComponent(sort)}&dir=${encodeURIComponent(dir)}&q=${encodeURIComponent(q)}`;
  const rowsEl = document.getElementById('rows'); rowsEl.innerHTML = '<tr><td colspan="8" class="muted">Carregando…</td></tr>';
  try {
    const cols = getListColumns(entity); currentCols = cols;
    renderThead(cols);
    const resp = await api(url);
    if (resp.status === 401) {
      const rowsEl = document.getElementById('rows');
      rowsEl.innerHTML = `<tr><td colspan="${cols.length+2}" class="muted">Não autorizado (401). <a href="/login">Faça login</a> e tente novamente.</td></tr>`;
      document.getElementById('pageInfo').textContent = '';
      return;
    }
    if (!resp.ok) {
      let msg = `Falha (${resp.status})`;
      try { const e = await resp.json(); if (e?.error?.message) msg = e.error.message; } catch(_){ }
      rowsEl.innerHTML = `<tr><td colspan="${cols.length+2}" class="muted">${escapeHtml(msg)}</td></tr>`;
      return;
    }
    const json = await resp.json(); total = json.total||0;
    currentRows = json.data||[];
    const rows = currentRows.map(r => fmtRow(r, cols)).join('');
    rowsEl.innerHTML = rows || `<tr><td colspan="${cols.length+2}" class="muted">Sem dados</td></tr>`;
    document.getElementById('pageInfo').textContent = `Página ${json.page||1} de ${Math.max(1, Math.ceil(total/limit))}`;
  } catch(e){ rowsEl.innerHTML = `<tr><td colspan=\"6\" class=\"muted\">Erro: ${escapeHtml(e.message||'indisponível')}</td></tr>`; }
}

document.getElementById('btnBuscar').addEventListener('click', ()=> { page = 1; load(); });
document.getElementById('btnLimpar').addEventListener('click', ()=> { const q = document.getElementById('q'); if (q) { q.value=''; } page = 1; load(); });
document.getElementById('prev').addEventListener('click', ()=> { if (page>1) { page--; load(); } });
document.getElementById('next').addEventListener('click', ()=> { const max = Math.max(1, Math.ceil(total/limit)); if (page<max) { page++; load(); } });
document.getElementById('btnNovo').addEventListener('click', async ()=> { document.getElementById('dlgTitle').textContent='Novo'; const formAlert = document.getElementById('formAlert'); if (formAlert) { formAlert.textContent=''; formAlert.style.display='none'; } await renderForm('create'); document.getElementById('dlgForm').showModal(); });
document.getElementById('btnCancelar').addEventListener('click', ()=> { document.getElementById('dlgForm').close(); });
// Controles do modal
(function(){
  const dlg = document.getElementById('dlgForm');
  const form = dlg.querySelector('form.card');
  const fields = document.getElementById('formFields');
  const footer = form.querySelector('.header');
  let maximized = false; let minimized = false;
  document.getElementById('dlgClose').addEventListener('click', ()=> dlg.close());
  document.getElementById('dlgMin').addEventListener('click', ()=>{
    minimized = !minimized;
    fields.style.display = minimized ? 'none' : '';
    footer.style.display = minimized ? 'none' : '';
    dlg.style.height = minimized ? 'auto' : '';
    dlg.style.width = minimized ? 'auto' : '';
  });
  document.getElementById('dlgMax').addEventListener('click', ()=>{
    maximized = !maximized;
    dlg.style.width = maximized ? '90vw' : '';
    dlg.style.height = maximized ? '90vh' : '';
  });
})();

// Controles padronizados para o diálogo de visualização
(function(){
  const dlg = document.getElementById('dlgView');
  if (!dlg) return;
  const content = document.getElementById('viewContent');
  const btnClose = document.getElementById('viewClose');
  const btnMin = document.getElementById('viewMin');
  const btnMax = document.getElementById('viewMax');
  let maximized = false; let minimized = false;
  if (btnClose) btnClose.addEventListener('click', ()=> dlg.close());
  if (btnMin) btnMin.addEventListener('click', ()=>{
    minimized = !minimized;
    if (content) content.style.display = minimized ? 'none' : '';
    dlg.style.height = minimized ? 'auto' : '';
    dlg.style.width = minimized ? 'auto' : '';
  });
  if (btnMax) btnMax.addEventListener('click', ()=>{
    maximized = !maximized;
    dlg.style.width = maximized ? '90vw' : '';
    dlg.style.height = maximized ? '90vh' : '';
  });
})();

document.getElementById('rows').addEventListener('click', async (ev)=>{
  const t = ev.target; const act = t.getAttribute('data-act'); const id = Number(t.getAttribute('data-id')||0);
  if (act === 'edit') {
    const resp = await api(`${baseUrl}/${id}`);
    if (!resp.ok) {
      let msg = `Falha ao carregar (${resp.status})`;
      try { const e = await resp.json(); msg = e?.error?.message || msg; } catch(_){ }
      window.alert(msg);
      return;
    }
    const { data } = await resp.json();
    document.getElementById('dlgTitle').textContent = `Editar #${id}`;
    await renderForm('edit', data);
    document.getElementById('dlgForm').setAttribute('data-id', String(id));
    const formAlert = document.getElementById('formAlert'); if (formAlert) { formAlert.textContent=''; formAlert.style.display='none'; }
    document.getElementById('dlgForm').showModal();
  }
  if (act === 'del') {
    const ok = await confirmDialog('Confirma excluir?');
    if (!ok) return;
    const resp = await api(`${baseUrl}/${id}`, { method:'DELETE' });
    if (resp.status === 204) { load(); } else { const e = await resp.json().catch(()=>({})); window.alert('Não foi possível excluir: '+ (e?.error?.message||resp.status)); }
  }
  if (act === 'view') {
    const resp = await api(`${baseUrl}/${id}`);
    if (!resp.ok) {
      let msg = `Falha ao carregar (${resp.status})`;
      try { const e = await resp.json(); msg = e?.error?.message || msg; } catch(_){ }
      window.alert(msg);
      return;
    }
    const { data } = await resp.json();
    const cont = document.getElementById('viewContent'); cont.innerHTML = '';
    const cfg = getEntityConfig(entity);
    cfg.fields.forEach(f => {
      const g = document.createElement('div'); g.className='form-group';
      const lb = document.createElement('label'); lb.textContent = f.label; g.appendChild(lb);
      const vv = document.createElement('div'); vv.className='muted';
      let val = data[f.id];
      if (Array.isArray(val)) val = val.join(', ');
      vv.textContent = String(val ?? ''); g.appendChild(vv);
      cont.appendChild(g);
    });
    document.getElementById('dlgView').showModal();
  }
});

// Seleção de linhas
document.getElementById('rows').addEventListener('change', (ev)=>{
  const t = ev.target; if (t && t.matches('input[type=checkbox][data-id]')) { const id = Number(t.getAttribute('data-id')); if (t.checked) selectedIds.add(id); else selectedIds.delete(id); }
});

document.getElementById('dlgForm').addEventListener('close', ()=> { document.getElementById('dlgForm').removeAttribute('data-id'); });
document.getElementById('btnSalvar').addEventListener('click', async (ev)=>{
  ev.preventDefault();
  const idAttr = document.getElementById('dlgForm').getAttribute('data-id');
  const id = idAttr ? Number(idAttr) : 0;
  const payload = collectPayload();
  // Limpa mensagens anteriores
  Array.from(document.querySelectorAll('#formFields .error-text')).forEach(el => { el.textContent=''; el.style.display='none'; });
  const v = validatePayloadDetailed(payload);
  if (v.global.length) {
    const formAlert = document.getElementById('formAlert');
    formAlert.innerHTML = '<strong>Corrija os campos:</strong> ' + v.global.join(' • ');
    formAlert.style.display = '';
    Object.entries(v.fields).forEach(([fid, msg]) => {
      const el = document.getElementById('err_'+fid); if (el) { el.textContent = msg; el.style.display = ''; }
    });
    return;
  } else {
    const formAlert = document.getElementById('formAlert'); formAlert.textContent=''; formAlert.style.display='none';
  }
  // Decide JSON ou FormData quando houver arquivos
  const hasFile = !!document.querySelector('#formFields input[type=file]');
  let body; let headers = {};
  if (hasFile) {
    const fd = new FormData();
    Object.entries(payload).forEach(([k,v])=> fd.append(k, String(v)));
    const fEl = document.getElementById('f_logo_file');
    if (fEl && fEl.files && fEl.files[0]) fd.append('logo_file', fEl.files[0]);
    body = fd;
  } else {
    body = JSON.stringify(payload);
    headers = { 'Content-Type':'application/json' };
  }
  const url = id ? `${baseUrl}/${id}` : baseUrl;
  const method = id ? 'PUT' : 'POST';
  const resp = await api(url, { method, body, headers });
  if (resp.status === 401) {
    const formAlert = document.getElementById('formAlert'); formAlert.textContent = 'Não autorizado (401). Faça login novamente ou verifique suas permissões.'; formAlert.style.display='';
    return;
  }
  if (resp.ok || resp.status === 201) { document.getElementById('dlgForm').close(); load(); }
  else { const e = await resp.json().catch(()=>({})); const formAlert = document.getElementById('formAlert'); formAlert.textContent = 'Falha: ' + (e?.error?.message||resp.status); formAlert.style.display=''; }
});

setTimeout(load, 50);

// Autocomplete no campo Nome do novo usuário usando clientes (3+ caracteres)
function setupUserNameAutocomplete(){
  const nameInput = document.getElementById('f_name');
  const emailInput = document.getElementById('f_email');
  if (!nameInput) return;
  let dl = document.getElementById('user_name_suggestions');
  if (!dl) { dl = document.createElement('datalist'); dl.id = 'user_name_suggestions'; document.body.appendChild(dl); }
  nameInput.setAttribute('list', 'user_name_suggestions');
  const map = new Map(); let last = '';
  const debounce = (fn, ms) => { let t; return (...args)=>{ clearTimeout(t); t = setTimeout(()=>fn(...args), ms); }; };
  const fetchClients = async (term) => {
    try {
      const url = `/api/clientes?q=${encodeURIComponent(term)}&per_page=10`;
      const resp = await api(url, { headers: { 'Accept':'application/json' } });
      const js = await resp.json().catch(()=>({}));
      const list = (js.data && Array.isArray(js.data)) ? js.data : (Array.isArray(js.rows) ? js.rows : []);
      dl.innerHTML = ''; map.clear();
      list.forEach(c => {
        const nm = c.nome || c.name || ''; if (!nm) return;
        const em = c.email || '';
        const opt = document.createElement('option'); opt.value = nm; dl.appendChild(opt);
        map.set(nm.toLowerCase(), { nome:nm, email:em });
      });
    } catch(_){ /* silencioso */ }
  };
  nameInput.addEventListener('input', debounce(()=>{
    const q = (nameInput.value||'').trim();
    if (q.length < 3) { dl.innerHTML=''; map.clear(); return; }
    if (q === last) return; last = q; fetchClients(q);
  }, 300));
  nameInput.addEventListener('change', ()=>{
    const sel = map.get((nameInput.value||'').toLowerCase());
    if (sel && emailInput && !emailInput.value) emailInput.value = sel.email || '';
  });
}

// Autocomplete para vínculos de vendedores com Cliente e Usuário
function setupVendedorLinkAutocomplete(){
  const cliInput = document.getElementById('f_cliente');
  const cliIdInput = document.getElementById('f_cliente_id');
  const userInput = document.getElementById('f_user');
  const userIdInput = document.getElementById('f_user_id');
  const nomeInput = document.getElementById('f_nome');
  const emailInput = document.getElementById('f_email');
  const telInput = document.getElementById('f_telefone');
  const mkDatalist = (id) => { let dl = document.getElementById(id); if (!dl) { dl = document.createElement('datalist'); dl.id = id; document.body.appendChild(dl); } return dl; };
  // Clientes
  if (cliInput) {
    const dlCli = mkDatalist('vendedor_cliente_suggestions');
    cliInput.setAttribute('list', dlCli.id);
    const map = new Map(); let last = '';
    const debounce = (fn, ms) => { let t; return (...args)=>{ clearTimeout(t); t = setTimeout(()=>fn(...args), ms); }; };
    const fetchClients = async (term) => {
      try {
        const url = `/api/clientes?q=${encodeURIComponent(term)}&limit=10&sort=nome&dir=ASC`;
        const resp = await api(url, { headers: { 'Accept':'application/json' } });
        const js = await resp.json().catch(()=>({}));
        const list = Array.isArray(js.data) ? js.data : [];
        dlCli.innerHTML = ''; map.clear();
        list.forEach(c => {
          const nm = c.nome || c.nome_fantasia || c.razao_social || '';
          const em = c.email || '';
          const tel = c.telefone_celular || c.telefone_fixo || '';
          const opt = document.createElement('option'); opt.value = nm; dlCli.appendChild(opt);
          map.set(nm.toLowerCase(), { id: c.id, nome: nm, email: em, telefone: tel });
        });
      } catch(_){ /* silencioso */ }
    };
    cliInput.addEventListener('input', debounce(()=>{
      const q = (cliInput.value||'').trim(); if (q.length < 3) { dlCli.innerHTML=''; map.clear(); if (cliIdInput) cliIdInput.value=''; return; }
      if (q === last) return; last = q; fetchClients(q);
    }, 300));
    cliInput.addEventListener('change', ()=>{
      const sel = map.get((cliInput.value||'').toLowerCase());
      if (sel) {
        if (cliIdInput) cliIdInput.value = String(sel.id||'');
        if (nomeInput && !nomeInput.value) nomeInput.value = sel.nome || '';
        if (emailInput && !emailInput.value) emailInput.value = sel.email || '';
        if (telInput && !telInput.value) telInput.value = sel.telefone || '';
      }
    });
  }
  // Usuários
  if (userInput) {
    const dlUser = mkDatalist('vendedor_user_suggestions');
    userInput.setAttribute('list', dlUser.id);
    const mapU = new Map(); let lastU = '';
    const debounce = (fn, ms) => { let t; return (...args)=>{ clearTimeout(t); t = setTimeout(()=>fn(...args), ms); }; };
    const fetchUsers = async (term) => {
      try {
        const url = `/api/auth/users?q=${encodeURIComponent(term)}&limit=10&sort=name&dir=ASC`;
        const resp = await api(url, { headers: { 'Accept':'application/json' } });
        const js = await resp.json().catch(()=>({}));
        const list = Array.isArray(js.data) ? js.data : [];
        dlUser.innerHTML=''; mapU.clear();
        list.forEach(u => {
          const nm = u.name || '';
          const em = u.email || '';
          const opt = document.createElement('option'); opt.value = nm; dlUser.appendChild(opt);
          mapU.set(nm.toLowerCase(), { id: u.id, nome: nm, email: em });
        });
      } catch(_){ /* silencioso */ }
    };
    userInput.addEventListener('input', debounce(()=>{
      const q = (userInput.value||'').trim(); if (q.length < 2) { dlUser.innerHTML=''; mapU.clear(); if (userIdInput) userIdInput.value=''; return; }
      if (q === lastU) return; lastU = q; fetchUsers(q);
    }, 300));
    userInput.addEventListener('change', ()=>{
      const sel = mapU.get((userInput.value||'').toLowerCase());
      if (sel) {
        if (userIdInput) userIdInput.value = String(sel.id||'');
        if (nomeInput && !nomeInput.value) nomeInput.value = sel.nome || '';
        if (emailInput && !emailInput.value) emailInput.value = sel.email || '';
      }
    });
  }
}

// Autocomplete de busca (3+ caracteres) e Enter para buscar
(function(){
  const q = document.getElementById('q');
  if (!q) return;
  const dlId = 'dl_q';
  const dl = document.createElement('datalist'); dl.id = dlId; document.body.appendChild(dl);
  q.setAttribute('list', dlId);
  let lastTerm = '';
  const debounced = (fn, ms) => { let t; return (...args)=>{ clearTimeout(t); t = setTimeout(()=>fn(...args), ms); }; };
  const fetchSugs = async (term) => {
    try {
      const url = `/api/cadastros/${entity}?limit=10&sort=nome&dir=ASC&q=${encodeURIComponent(term)}`;
      const resp = await api(url); if (!resp.ok) return;
      const json = await resp.json(); const rows = json.data||[];
      dl.innerHTML = '';
      rows.forEach(r => { const o = document.createElement('option'); o.value = r.nome || r.razao_social || r.email || String(r.id); dl.appendChild(o); });
    } catch(_){ /* silencioso */ }
  };
  q.addEventListener('input', debounced(()=>{
    const term = q.value.trim(); if (term.length >= 3 && term !== lastTerm) { lastTerm = term; fetchSugs(term); }
  }, 300));
  q.addEventListener('keydown', (ev)=>{ if (ev.key === 'Enter') { ev.preventDefault(); page=1; load(); } });
})();

// Exportação CSV/XLSX
document.getElementById('btnExport').addEventListener('click', ()=>{
  if (!currentRows.length) { window.alert('Nada para exportar'); return; }
  const cols = currentCols.map(c => c.id);
  const headers = currentCols.map(c => c.label);
  const data = currentRows.map(r => cols.map(k => r[k] ?? ''));
  // CSV
  const csv = [headers].concat(data).map(row => row.map(v => '"'+String(v).replace(/"/g,'""')+'"').join(',')).join('\n');
  const blobCsv = new Blob([csv], {type:'text/csv;charset=utf-8;'});
  const a = document.createElement('a'); a.href = URL.createObjectURL(blobCsv); a.download = `${entity}_listagem.csv`; a.click();
  // XLSX (se lib disponível)
  if (window.XLSX) {
    const ws = XLSX.utils.aoa_to_sheet([headers, ...data]);
    const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Dados');
    XLSX.writeFile(wb, `${entity}_listagem.xlsx`);
  } else {
    console.warn('Biblioteca XLSX não carregada; exportado apenas CSV.');
  }
});

// Importação
document.getElementById('btnImport').addEventListener('click', ()=>{
  // disponibiliza modelos dinâmicos
  const headers = getListColumns(entity).map(c => c.id);
  const csv = headers.join(',') + '\n';
  const blobCsv = new Blob([csv], {type:'text/csv'});
  const urlCsv = URL.createObjectURL(blobCsv);
  const aCsv = document.getElementById('tplCsv'); aCsv.href = urlCsv; aCsv.download = `${entity}_modelo.csv`;
  // XLSX se disponível
  if (window.XLSX) {
    const ws = XLSX.utils.aoa_to_sheet([headers]); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 'Modelo');
    const bin = XLSX.write(wb, {bookType:'xlsx', type:'array'});
    const urlX = URL.createObjectURL(new Blob([bin], {type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}));
    const aX = document.getElementById('tplXlsx'); aX.href = urlX; aX.download = `${entity}_modelo.xlsx`;
  } else {
    const aX = document.getElementById('tplXlsx'); aX.href = '#'; aX.setAttribute('disabled','');
  }
  document.getElementById('importAlert').style.display='none'; document.getElementById('importAlert').textContent='';
  document.getElementById('dlgImport').showModal();
});
document.getElementById('btnCancelImport').addEventListener('click', ()=>{ document.getElementById('dlgImport').close(); });
document.getElementById('btnDoImport').addEventListener('click', async (ev)=>{
  ev.preventDefault();
  const file = document.getElementById('fileImport').files[0];
  if (!file) { const al = document.getElementById('importAlert'); al.textContent='Selecione um arquivo.'; al.style.display=''; return; }
  if (file.size > 2*1024*1024) { const al = document.getElementById('importAlert'); al.textContent='Arquivo acima de 2MB.'; al.style.display=''; return; }
  const ext = file.name.toLowerCase().split('.').pop();
  const headers = getListColumns(entity).map(c => c.id);
  let rows = [];
  try {
    if (ext === 'csv') {
      const text = await file.text();
      const lines = text.split(/\r?\n/).filter(l=>l.trim().length);
      const hdr = lines.shift().split(',').map(s=>s.trim());
      // valida cabeçalho mínimo
      if (!hdr.length) throw new Error('Cabeçalho vazio');
      rows = lines.map(l => {
        const vals = l.split(','); const obj = {}; hdr.forEach((h,i)=>{ obj[h] = (vals[i]||'').replace(/^"|"$/g,''); }); return obj;
      });
    } else if (ext === 'xlsx' && window.XLSX) {
      const buf = await file.arrayBuffer(); const wb = XLSX.read(buf, {type:'array'}); const ws = wb.Sheets[wb.SheetNames[0]]; rows = XLSX.utils.sheet_to_json(ws);
    } else { throw new Error('Formato não suportado'); }
  } catch(e){ const al = document.getElementById('importAlert'); al.textContent = 'Falha ao ler arquivo: '+ (e.message||e); al.style.display=''; return; }
  // validações simples
  const errs = [];
  rows.forEach((r,idx)=>{ if (!r.nome && entity !== 'usuarios') errs.push(`Linha ${idx+2}: nome ausente`); if (entity==='usuarios' && !r.email) errs.push(`Linha ${idx+2}: e-mail ausente`); });
  if (errs.length) { const al = document.getElementById('importAlert'); al.innerHTML = '<strong>Corrija:</strong> '+ errs.join(' • '); al.style.display=''; return; }
  // envia ao backend, se disponível
  try {
    const resp = await api(`${baseUrl}/import`, { method:'POST', body: JSON.stringify({ rows }) });
    if (!resp.ok) throw new Error('Serviço indisponível');
    document.getElementById('dlgImport').close(); load();
  } catch(e){ const al = document.getElementById('importAlert'); al.textContent = 'Importação local concluída, serviço de backend não disponível.'; al.style.display=''; }
});

// PDF simples via janela de impressão com cabeçalho e logo
document.getElementById('btnPdf').addEventListener('click', ()=>{
  const win = window.open('', '_blank'); if (!win) { window.alert('Não foi possível abrir janela'); return; }
  const filters = `Busca: ${document.getElementById('q').value||''} • Ordenação: ${document.getElementById('sort').value||'id'} ${document.getElementById('dir').value||'DESC'}`;
  const headers = currentCols.map(c => c.label);
  const source = selectedIds.size ? currentRows.filter(r => selectedIds.has(Number(r.id))) : currentRows;
  const rows = source.map(r => currentCols.map(c => {
    let v = r[c.id];
    if (Array.isArray(v)) v = v.join(', ');
    return escapeHtml(String(v ?? ''));
  }));
  const table = '<table style="width:100%; border-collapse:collapse;">'+
    '<thead><tr>'+ headers.map(h=>`<th style=\"border:1px solid #ccc; padding:4px; text-align:left;\">${h}</th>`).join('') + '</tr></thead>'+
    '<tbody>'+ rows.map(row=>'<tr>'+row.map(v=>`<td style=\"border:1px solid #ccc; padding:4px;\">${v}</td>`).join('')+'</tr>').join('') +'</tbody></table>';
  win.document.write(`<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Relatório ${escapeHtml(entity)}</title><style>@media print { body{font-family: Arial, sans-serif;} table{font-size:12px;} } </style></head><body>`+
    `<div style="display:flex; align-items:center; gap:.5rem; margin-bottom:.5rem;"><img src="/assets/logo.png" alt="Logo" style="height:40px;"/><h2 style="margin:0;">Relatório de ${escapeHtml(entity)}</h2></div>`+
    `<div style="margin-bottom:.5rem; font-size:.9rem; color:#555;">${escapeHtml(filters)}</div>`+
    table+
    `</body></html>`);
  win.document.close();
  const doPrint = () => {
    try {
      if (window.PrintControl && typeof window.PrintControl.safeWindowPrint === 'function') {
        window.PrintControl.safeWindowPrint(win, { copies: 1, lockMs: 2000 });
      } else {
        win.focus(); win.print(); win.close();
      }
    } catch(_) { try { win.focus(); win.print(); win.close(); } catch(__){} }
  };
  // Espera o load para garantir renderização antes de imprimir
  try { win.addEventListener('load', doPrint); } catch(_) { setTimeout(doPrint, 250); }
});

// Edição em massa
document.getElementById('btnBulk').addEventListener('click', ()=>{
  if (!selectedIds.size) { window.alert('Selecione registros na lista.'); return; }
  const cont = document.getElementById('bulkFields'); cont.innerHTML = '';
  const cfg = getEntityConfig(entity);
  cfg.fields.forEach(f => {
    const g = document.createElement('div'); g.className='form-group';
    const lb = document.createElement('label'); lb.textContent = `${f.label} (aplicar?)`; g.appendChild(lb);
    const chk = document.createElement('input'); chk.type='checkbox'; chk.id = 'bulk_apply_'+f.id; chk.style.marginRight='.25rem'; g.appendChild(chk);
    let input;
    if (f.type==='select' || f.type==='text' || f.type==='email' || f.type==='number' || f.type==='url') { input = document.createElement('input'); input.type = (f.type==='select'?'text':f.type); }
    else if (f.type==='checkbox') { input = document.createElement('input'); input.type='checkbox'; }
    else { input = document.createElement('input'); input.type='text'; }
    input.id = 'bulk_'+f.id; g.appendChild(input);
    cont.appendChild(g);
  });
  document.getElementById('bulkAlert').style.display='none'; document.getElementById('bulkAlert').textContent='';
  document.getElementById('bulkProgress').textContent = `${selectedIds.size} registros selecionados`;
  document.getElementById('dlgBulk').showModal();
});
document.getElementById('btnCancelBulk').addEventListener('click', ()=>{ document.getElementById('dlgBulk').close(); });
document.getElementById('btnDoBulk').addEventListener('click', async (ev)=>{
  ev.preventDefault();
  const cfg = getEntityConfig(entity);
  const updates = {};
  cfg.fields.forEach(f => { const apply = document.getElementById('bulk_apply_'+f.id)?.checked; if (apply) { const el = document.getElementById('bulk_'+f.id); updates[f.id] = (f.type==='checkbox') ? !!el.checked : String(el.value||''); } });
  if (!Object.keys(updates).length) { const al = document.getElementById('bulkAlert'); al.textContent='Selecione ao menos um campo para aplicar.'; al.style.display=''; return; }
  const ids = Array.from(selectedIds);
  let ok=0, fail=0;
  for (const id of ids) {
    try {
      const resp = await api(`${baseUrl}/${id}`, { method:'PUT', body: JSON.stringify(updates) });
      if (!resp.ok) fail++; else ok++;
      document.getElementById('bulkProgress').textContent = `Processados: ${ok+fail}/${ids.length} (ok: ${ok}, falhas: ${fail})`;
    } catch(_){ fail++; document.getElementById('bulkProgress').textContent = `Processados: ${ok+fail}/${ids.length} (ok: ${ok}, falhas: ${fail})`; }
  }
  if (fail) { const al = document.getElementById('bulkAlert'); al.textContent = `Concluído com falhas (${fail}).`; al.style.display=''; } else { document.getElementById('dlgBulk').close(); }
  load();
});

// Carrega XLSX via CDN se possível
(function(){
  const s = document.createElement('script'); s.src = 'https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js'; s.async = true; document.head.appendChild(s);
})();

// Confirmação consistente via dialog
function confirmDialog(message){
  return new Promise(resolve => {
    const dlg = document.getElementById('dlgConfirm');
    const msgEl = document.getElementById('confirmMsg');
    const btnYes = document.getElementById('btnConfirmYes');
    const btnNo = document.getElementById('btnConfirmNo');
    msgEl.textContent = String(message||'Confirmar ação?');
    const onYes = ()=>{ cleanup(); resolve(true); };
    const onNo = ()=>{ cleanup(); resolve(false); };
    const cleanup = ()=>{
      btnYes.removeEventListener('click', onYes);
      btnNo.removeEventListener('click', onNo);
      dlg.close();
    };
    btnYes.addEventListener('click', onYes);
    btnNo.addEventListener('click', onNo);
    dlg.showModal();
  });
}

function digits(s){ return String(s||'').replace(/\D/g,''); }
function formatCpf(s){ const d = digits(s).slice(0,11); if (d.length<=3) return d; if (d.length<=6) return d.replace(/(\d{3})(\d+)/,'$1.$2'); if (d.length<=9) return d.replace(/(\d{3})(\d{3})(\d+)/,'$1.$2.$3'); return d.replace(/(\d{3})(\d{3})(\d{3})(\d{0,2})/,'$1.$2.$3-$4'); }
function formatCnpj(s){ const d = digits(s).slice(0,14); if (d.length<=2) return d; if (d.length<=5) return d.replace(/(\d{2})(\d+)/,'$1.$2'); if (d.length<=8) return d.replace(/(\d{2})(\d{3})(\d+)/,'$1.$2.$3'); if (d.length<=12) return d.replace(/(\d{2})(\d{3})(\d{3})(\d+)/,'$1.$2.$3/$4'); return d.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{0,2})/,'$1.$2.$3/$4-$5'); }
function formatPhone(s){ const d = digits(s).slice(0,11); if (d.length<=2) return d; if (d.length<=6) return d.replace(/(\d{2})(\d+)/,'($1) $2'); if (d.length<=10) return d.replace(/(\d{2})(\d{4})(\d{0,4})/,'($1) $2-$3'); return d.replace(/(\d{2})(\d{5})(\d{0,4})/,'($1) $2-$3'); }

// Lookup de CNPJ e auto-preenchimento
async function lookupCnpjAndFill(cnpjDigits){
  try {
    // Tenta API pública com bom CORS
    const resp = await fetch(`https://publica.cnpj.ws/cnpj/${cnpjDigits}`);
    if (!resp.ok) return;
    const j = await resp.json();
    // Mapeia campos
    const fantasia = j.nome_fantasia || j.estabelecimento?.nome_fantasia || '';
    const razao = j.razao_social || '';
    const email = j.estabelecimento?.email || j.email || '';
    const tel = j.estabelecimento?.telefone1 || j.estabelecimento?.telefone || j.telefone || '';
    const fNome = document.getElementById('f_nome'); if (fNome && !fNome.value.trim() && fantasia) fNome.value = fantasia;
    const fRs = document.getElementById('f_razao_social'); if (fRs && !fRs.value.trim() && razao) fRs.value = razao;
    const fEmail = document.getElementById('f_email'); if (fEmail && !fEmail.value.trim() && email) fEmail.value = email;
    const fTel = document.getElementById('f_telefone'); if (fTel && !fTel.value.trim() && tel) fTel.value = formatPhone(tel);
    const err = document.getElementById('err_cnpj'); if (err) { err.style.display=''; err.textContent='Dados preenchidos automaticamente pelo CNPJ.'; setTimeout(()=>{ err.style.display='none'; }, 3000); }
  } catch(_){ /* ignora falhas */ }
}
</script>