#!/usr/bin/env node
// =============================================================================
// AgenciaPsi — Dashboard Generator
// =============================================================================
// Uso:
//   node generate-dashboard.cjs              → usa backup mais recente
//   node generate-dashboard.cjs 2026-04-17   → usa backup de data específica
//
// Lê de:  ./backups/YYYY-MM-DD/schema.sql
// Lê de:  ./db.config.json  (domínios, cores e infraestrutura)
// Gera:   ./agenciapsi-db-dashboard.html  (na mesma pasta do script)
// =============================================================================

const fs = require('fs');
const path = require('path');

const ROOT = __dirname;
const BACKUPS_DIR = path.join(ROOT, 'backups');
const OUTPUT_FILE = path.join(ROOT, 'agenciapsi-db-dashboard.html');
const CONFIG_FILE = path.join(ROOT, 'db.config.json');

// ---------------------------------------------------------------------------
// Carrega config (domínios, cores e infraestrutura)
// ---------------------------------------------------------------------------
if (!fs.existsSync(CONFIG_FILE)) {
    console.error(`✖  Config não encontrada: ${CONFIG_FILE}`);
    process.exit(1);
}
const CONFIG = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
const DOMAIN_TABLES = CONFIG.domains || {};
const DOMAIN_COLORS = CONFIG.domainColors || {};
const INFRASTRUCTURE = CONFIG.infrastructure || {};

// ---------------------------------------------------------------------------
// 1. Resolve qual schema.sql usar
// ---------------------------------------------------------------------------
function resolveSchema() {
    const arg = process.argv[2];

    if (!fs.existsSync(BACKUPS_DIR)) {
        console.error(`✖  Pasta não encontrada: ${BACKUPS_DIR}`);
        console.error(`   Rode primeiro: node db.cjs backup`);
        process.exit(1);
    }

    const available = fs
        .readdirSync(BACKUPS_DIR)
        .filter((f) => /^\d{4}-\d{2}-\d{2}$/.test(f))
        .sort()
        .reverse();

    if (available.length === 0) {
        console.error('✖  Nenhum backup encontrado em database-novo/backups/');
        console.error('   Rode primeiro: node db.cjs backup');
        process.exit(1);
    }

    const date = arg && /^\d{4}-\d{2}-\d{2}$/.test(arg) ? arg : available[0];

    if (!available.includes(date)) {
        console.error(`✖  Backup não encontrado para: ${date}`);
        console.error(`   Disponíveis: ${available.join(', ')}`);
        process.exit(1);
    }

    const schemaPath = path.join(BACKUPS_DIR, date, 'schema.sql');
    if (!fs.existsSync(schemaPath)) {
        console.error(`✖  schema.sql não encontrado em backups/${date}/`);
        process.exit(1);
    }

    return { schemaPath, date, available };
}

// ---------------------------------------------------------------------------
// 2. Parse do schema.sql — extrai tabelas, colunas e FKs
// ---------------------------------------------------------------------------
function parseSchema(content) {
    const tables = {};

    // Tabelas public.*
    const tableRe = /CREATE TABLE (public\.\S+)\s*\(([\s\S]*?)\);/gm;
    let m;
    while ((m = tableRe.exec(content)) !== null) {
        const name = m[1].replace('public.', '');
        const body = m[2];
        const columns = [];

        for (let line of body.split('\n')) {
            line = line.trim().replace(/,$/, '');
            if (!line || line.startsWith('--')) continue;
            if (/^(CONSTRAINT|PRIMARY KEY|UNIQUE|CHECK|FOREIGN KEY|EXCLUDE)/i.test(line)) continue;

            const col = line.match(
                /^(\w+)\s+([\w\[\]"()\s,]+?)(?:\s+DEFAULT\s+|\s+NOT NULL|\s+NULL|\s+GENERATED|\s+REFERENCES\s|$)/
            );
            if (col) {
                columns.push({
                    name: col[1],
                    type: col[2].trim().split('(')[0].trim(),
                    pk: col[1] === 'id'
                });
            }
        }

        tables[name] = { columns, fks: [] };
    }

    // FKs via ALTER TABLE ... ADD CONSTRAINT ... FOREIGN KEY
    const fkRe = /ALTER TABLE ONLY public\.(\w+)\s+ADD CONSTRAINT \S+ FOREIGN KEY \((\w+)\) REFERENCES public\.(\w+)\((\w+)\)/gm;
    while ((m = fkRe.exec(content)) !== null) {
        const [, fromTable, fromCol, toTable, toCol] = m;
        if (tables[fromTable]) {
            tables[fromTable].fks.push({ from_col: fromCol, to_table: toTable, to_col: toCol });
        }
    }

    // Views
    const viewRe = /CREATE(?:\s+OR REPLACE)?\s+VIEW\s+public\.(\S+)\s+AS/gm;
    const views = [];
    while ((m = viewRe.exec(content)) !== null) views.push(m[1]);

    return { tables, views };
}

// ---------------------------------------------------------------------------
// 3. Monta os domínios
//    Tabelas novas que ainda não estão mapeadas vão para "Outros"
// ---------------------------------------------------------------------------
function buildDomains(tables) {
    const mapped = new Set(Object.values(DOMAIN_TABLES).flat());
    const others = Object.keys(tables).filter((t) => !mapped.has(t) && t !== '_db_migrations');

    const domains = {};
    for (const [domain, list] of Object.entries(DOMAIN_TABLES)) {
        const present = list.filter((t) => tables[t]);
        if (present.length > 0) domains[domain] = present;
    }
    if (others.length > 0) {
        domains['Outros'] = others;
        DOMAIN_COLORS['Outros'] = '#6b7280';
    }

    return domains;
}

// ---------------------------------------------------------------------------
// 4. Gera o HTML final (standalone, sem dependências externas de JS)
// ---------------------------------------------------------------------------
function generateHTML(tables, views, domains, date, available) {
    const totalFKs = Object.values(tables).reduce((a, t) => a + t.fks.length, 0);
    const totalCols = Object.values(tables).reduce((a, t) => a + t.columns.length, 0);
    const infraGroups = Object.keys(INFRASTRUCTURE).length;
    const infraItems = Object.values(INFRASTRUCTURE).reduce((a, g) => a + (g.items?.length || 0), 0);
    const generated = new Date().toLocaleString('pt-BR');

    // Slug por domínio — usado como id para scroll (ex: "SaaS / Planos" → "saas-planos")
    const slugify = (s) => s.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
    const domainSlugs = {};
    for (const d of Object.keys(domains)) domainSlugs[d] = slugify(d);

    // Serializa dados para embutir no HTML
    const jsonData = JSON.stringify({ tables, views, domains, slugs: domainSlugs });
    const jsonColors = JSON.stringify(DOMAIN_COLORS);
    const jsonInfra = JSON.stringify(INFRASTRUCTURE);

    return `<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AgenciaPsi DB · ${date}</title>
<style>
  @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
  :root{--bg:#0b0d12;--bg2:#111520;--bg3:#181e2d;--border:#1e2740;--border2:#263050;--text:#e2e8f8;--text2:#7d8fb3;--text3:#4a5a80;--accent:#6366f1;--accent2:#6ee7b7;--pk:#fbbf24;--fk:#f472b6;--ok:#34d399;--warn:#fbbf24;--pend:#f87171;--leg:#94a3b8}
  *{margin:0;padding:0;box-sizing:border-box}
  body{background:var(--bg);color:var(--text);font-family:'Space Grotesk',sans-serif;min-height:100vh;overflow-x:hidden}

  .topbar{position:sticky;top:0;z-index:100;background:rgba(11,13,18,.94);backdrop-filter:blur(12px);border-bottom:1px solid var(--border);padding:0 28px;height:56px;display:flex;align-items:center;gap:20px}
  .brand{font-weight:700;font-size:15px;letter-spacing:-.3px}.brand span{color:var(--accent)}
  .gen{font-size:11px;color:var(--text3);font-family:'IBM Plex Mono',monospace}
  .pills{display:flex;gap:10px;margin-left:auto}
  .pill{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text2);background:var(--bg3);border:1px solid var(--border);border-radius:20px;padding:4px 12px}
  .pill strong{color:var(--text);font-size:13px}
  .search{background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:6px 12px;color:var(--text);font-family:'Space Grotesk',sans-serif;font-size:13px;outline:none;width:200px;transition:border-color .2s,width .2s}
  .search:focus{border-color:var(--accent);width:280px}
  .search::placeholder{color:var(--text3)}

  .layout{display:flex;height:calc(100vh - 56px)}

  .sidebar{width:260px;flex-shrink:0;background:var(--bg2);border-right:1px solid var(--border);overflow-y:auto;padding:16px 0}
  .sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}
  .sb-h{font-size:10px;font-weight:600;letter-spacing:1px;text-transform:uppercase;color:var(--text3);padding:8px 20px 4px}
  .sb-i{display:flex;align-items:center;gap:10px;padding:7px 20px;cursor:pointer;font-size:13px;color:var(--text2);border-left:2px solid transparent;transition:all .15s;user-select:none}
  .sb-i:hover{color:var(--text);background:var(--bg3)}
  .sb-i.active{color:var(--text);border-left-color:var(--accent);background:rgba(99,102,241,.08)}
  .sb-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
  .sb-c{margin-left:auto;font-size:11px;color:var(--text3);font-family:'IBM Plex Mono',monospace}

  .main{flex:1;overflow-y:auto}
  .main::-webkit-scrollbar{width:5px}.main::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}

  .overview{padding:32px 36px;border-bottom:1px solid var(--border)}
  .ov-t{font-size:22px;font-weight:700;margin-bottom:6px}
  .ov-s{font-size:14px;color:var(--text2);margin-bottom:28px}
  .dgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:14px}
  .dc{background:var(--bg3);border:1px solid var(--border);border-radius:12px;padding:16px 18px;cursor:pointer;transition:all .2s;position:relative;overflow:hidden}
  .dc::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--c)}
  .dc:hover{border-color:var(--border2);transform:translateY(-1px)}
  .dc-n{font-size:14px;font-weight:600;margin-bottom:6px}
  .dc-m{font-size:12px;color:var(--text2);font-family:'IBM Plex Mono',monospace}
  .dc-m span{font-weight:600}

  .section{padding:28px 36px}
  .sec-h{display:flex;align-items:center;gap:14px;margin-bottom:20px}
  .sec-t{font-size:18px;font-weight:700}
  .sec-b{font-size:11px;font-family:'IBM Plex Mono',monospace;background:var(--bg3);border:1px solid var(--border);border-radius:20px;padding:3px 10px;color:var(--text2)}

  .tgrid{display:flex;flex-direction:column;gap:10px}
  .tc{background:var(--bg3);border:1px solid var(--border);border-radius:10px;overflow:hidden;transition:border-color .15s}
  .tc:hover{border-color:var(--border2)}.tc.hl{border-color:var(--accent)}
  .tc-h{display:flex;align-items:center;gap:12px;padding:12px 16px;cursor:pointer;user-select:none}
  .tc-n{font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:600}
  .tc-m{font-size:11px;color:var(--text3);font-family:'IBM Plex Mono',monospace}
  .tc-f{font-size:11px;color:var(--fk);font-family:'IBM Plex Mono',monospace;margin-left:4px}
  .tc-tg{margin-left:auto;color:var(--text3);font-size:11px;transition:transform .2s}
  .tc-tg.open{transform:rotate(180deg)}
  .tc-b{display:none;border-top:1px solid var(--border)}.tc-b.open{display:block}
  .cols{padding:6px 0}
  .cr{display:flex;align-items:center;gap:10px;padding:5px 16px;font-size:12px;font-family:'IBM Plex Mono',monospace;color:var(--text2)}
  .cr:hover{background:rgba(255,255,255,.02)}
  .bdg{font-size:9px;font-weight:700;letter-spacing:.5px;padding:1px 5px;border-radius:3px;width:26px;text-align:center;flex-shrink:0}
  .bdg.pk{background:rgba(251,191,36,.15);color:var(--pk)}.bdg.fk{background:rgba(244,114,182,.15);color:var(--fk)}.bdg.x{background:transparent}
  .cn{color:var(--text)}.ct{color:var(--text3);margin-left:auto;font-size:11px}
  .fksec{border-top:1px solid var(--border);padding:10px 16px}
  .fkt{font-size:10px;font-weight:600;letter-spacing:1px;color:var(--text3);text-transform:uppercase;margin-bottom:8px}
  .fkr{display:flex;align-items:center;gap:8px;font-size:12px;font-family:'IBM Plex Mono',monospace;color:var(--text2);padding:3px 0}
  .fka{color:var(--fk)}.fkl{color:var(--accent);cursor:pointer}.fkl:hover{text-decoration:underline}

  .vsec{padding:0 36px 32px}
  .vgrid{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
  .vc{background:rgba(110,231,183,.08);border:1px solid rgba(110,231,183,.2);border-radius:6px;padding:5px 12px;font-size:12px;font-family:'IBM Plex Mono',monospace;color:var(--accent2)}
  .empty{padding:40px;text-align:center;color:var(--text3);font-size:14px}
  mark{background:rgba(99,102,241,.3);color:#fff;border-radius:2px}

  /* Infraestrutura */
  .igroup{margin-bottom:28px}
  .igroup-h{display:flex;align-items:center;gap:10px;margin-bottom:14px}
  .igroup-t{font-size:15px;font-weight:600;letter-spacing:-.2px}
  .igroup-c{width:10px;height:10px;border-radius:50%;flex-shrink:0}
  .igrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
  .ic{background:var(--bg3);border:1px solid var(--border);border-radius:10px;padding:16px 18px;transition:border-color .15s;position:relative;overflow:hidden}
  .ic::before{content:'';position:absolute;top:0;left:0;bottom:0;width:3px;background:var(--c)}
  .ic:hover{border-color:var(--border2)}
  .ic-h{display:flex;align-items:center;gap:10px;margin-bottom:8px}
  .ic-n{font-size:14px;font-weight:600;flex:1;min-width:0}
  .ic-st{font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.6px;padding:2px 7px;border-radius:10px;flex-shrink:0;white-space:nowrap}
  .ic-st.ativo{background:rgba(52,211,153,.15);color:var(--ok)}
  .ic-st.pendente{background:rgba(248,113,113,.15);color:var(--pend)}
  .ic-st.planejado{background:rgba(251,191,36,.15);color:var(--warn)}
  .ic-st.legado{background:rgba(148,163,184,.2);color:var(--leg)}
  .ic-r{font-size:12px;color:var(--text2);margin-bottom:8px;line-height:1.5}
  .ic-e{font-size:10px;color:var(--text3);font-family:'IBM Plex Mono',monospace;margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}
  .ic-nt{font-size:11px;color:var(--text3);line-height:1.55;border-top:1px solid var(--border);padding-top:8px;margin-top:8px}
</style>
</head>
<body>
<div class="topbar">
  <div class="brand">Agência<span>Psi</span> DB</div>
  <span class="gen">${date} · ${generated}</span>
  <input class="search" id="si" placeholder="Buscar tabela ou coluna..." oninput="search(this.value)">
  <div class="pills">
    <div class="pill"><strong>${Object.keys(tables).length}</strong> tabelas</div>
    <div class="pill"><strong>${totalFKs}</strong> FKs</div>
    <div class="pill"><strong>${views.length}</strong> views</div>
    <div class="pill"><strong>${totalCols}</strong> colunas</div>
    <div class="pill"><strong>${infraItems}</strong> infra</div>
  </div>
</div>
<div class="layout">
  <nav class="sidebar" id="sb"></nav>
  <main class="main" id="mn"></main>
</div>
<script>
const D=${jsonData};
const C=${jsonColors};
const INFRA=${jsonInfra};
const INFRA_GROUPS=${infraGroups};
const INFRA_ITEMS=${infraItems};
const T2D={};
Object.entries(D.domains).forEach(([d,ts])=>ts.forEach(t=>T2D[t]=d));
let dom=null,view='overview',q='';
function gc(d){return C[d]||'#6b7280';}
function escapeHtml(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}

function buildSB(){
  let h=\`<div class="sb-h">Visão Geral</div>
  <div class="sb-i \${view==='overview'&&!dom?'active':''}" onclick="selOverview()">
    <div class="sb-dot" style="background:#6366f1"></div>Todos (tabelas)
    <span class="sb-c">\${Object.keys(D.tables).length}</span>
  </div>
  <div class="sb-i \${view==='infra'?'active':''}" onclick="selInfra()">
    <div class="sb-dot" style="background:#fbbf24"></div>Infraestrutura
    <span class="sb-c">\${INFRA_ITEMS}</span>
  </div>
  <div class="sb-h" style="margin-top:8px">Domínios</div>\`;
  for(const[d,ts]of Object.entries(D.domains)){
    h+=\`<div class="sb-i \${dom===d?'active':''}" onclick="scrollToDomain('\${D.slugs[d]}')">
      <div class="sb-dot" style="background:\${gc(d)}"></div>\${escapeHtml(d)}
      <span class="sb-c">\${ts.length}</span>
    </div>\`;
  }
  h+=\`<div class="sb-i" onclick="scrollToViews()">
    <div class="sb-dot" style="background:#6ee7b7"></div>Views
    <span class="sb-c">\${D.views.length}</span>
  </div>\`;
  document.getElementById('sb').innerHTML=h;
}

function buildMN(){
  const mn=document.getElementById('mn');
  let h='';
  if(q){
    const matches=Object.entries(D.tables).filter(([n,t])=>n.includes(q)||t.columns.some(c=>c.name.includes(q)));
    h+=\`<div class="section"><div class="sec-h"><div class="sec-t">"\${escapeHtml(q)}"</div><div class="sec-b">\${matches.length} tabelas</div></div><div class="tgrid">\`;
    h+=matches.length?matches.map(([n,t])=>card(n,t,q)).join(''):'<div class="empty">Nenhum resultado.</div>';
    h+='</div></div>';
  } else if(view==='infra'){
    h+=\`<div class="overview"><div class="ov-t">Infraestrutura</div>
    <div class="ov-s">Serviços, bibliotecas e ferramentas que o sistema usa · \${INFRA_GROUPS} grupos · \${INFRA_ITEMS} itens</div></div>
    <div class="section">\`;
    for(const[grupo,info]of Object.entries(INFRA)){
      const color=info.color||'#6b7280';
      h+=\`<div class="igroup">
        <div class="igroup-h">
          <div class="igroup-c" style="background:\${color}"></div>
          <div class="igroup-t" style="color:\${color}">\${escapeHtml(grupo)}</div>
          <div class="sec-b">\${info.items.length} itens</div>
        </div>
        <div class="igrid">\${info.items.map(item=>infraCard(item,color)).join('')}</div>
      </div>\`;
    }
    h+='</div>';
  } else {
    const ds=dom?{[dom]:D.domains[dom]}:D.domains;
    if(!dom){
      h+=\`<div class="overview"><div class="ov-t">AgenciaPsi — Banco de Dados</div>
      <div class="ov-s">Schema público · \${Object.keys(D.tables).length} tabelas · \${Object.values(D.tables).reduce((a,t)=>a+t.fks.length,0)} FKs · \${D.views.length} views</div>
      <div class="dgrid">\`;
      for(const[d,ts]of Object.entries(D.domains)){
        const fks=ts.reduce((a,t)=>a+(D.tables[t]?.fks?.length||0),0);
        h+=\`<div class="dc" style="--c:\${gc(d)}" onclick="scrollToDomain('\${D.slugs[d]}')">
          <div class="dc-n">\${escapeHtml(d)}</div>
          <div class="dc-m"><span style="color:\${gc(d)}">\${ts.length}</span> tabelas · \${fks} FKs</div>
        </div>\`;
      }
      h+='</div></div>';
    }
    for(const[d,ts]of Object.entries(ds)){
      h+=\`<div class="section" id="dom-\${D.slugs[d]||''}"><div class="sec-h">
        <div class="sec-t" style="color:\${gc(d)}">\${escapeHtml(d)}</div>
        <div class="sec-b">\${ts.length} tabelas</div>
      </div><div class="tgrid">\`;
      ts.forEach(n=>{if(D.tables[n])h+=card(n,D.tables[n],'');});
      h+='</div></div>';
    }
    if(!dom){
      h+=\`<div class="vsec" id="dom-views"><div class="sec-h">
        <div class="sec-t" style="color:#6ee7b7">Views</div>
        <div class="sec-b">\${D.views.length}</div>
      </div><div class="vgrid">\${D.views.map(v=>\`<div class="vc">\${v}</div>\`).join('')}</div></div>\`;
    }
  }
  mn.innerHTML=h;
}

function infraCard(item,color){
  const status=(item.status||'ativo').toLowerCase();
  return \`<div class="ic" style="--c:\${color}">
    <div class="ic-h">
      <div class="ic-n">\${escapeHtml(item.name)}</div>
      <div class="ic-st \${status}">\${escapeHtml(item.status||'ativo')}</div>
    </div>
    <div class="ic-r">\${escapeHtml(item.role||'')}</div>
    \${item.env?\`<div class="ic-e">\${escapeHtml(item.env)}</div>\`:''}
    \${item.notes?\`<div class="ic-nt">\${escapeHtml(item.notes)}</div>\`:''}
  </div>\`;
}

function card(name,t,hl){
  const fkCols=new Set(t.fks.map(f=>f.from_col));
  const c=gc(T2D[name]);
  const cols=t.columns.map(col=>{
    let n=col.name;
    if(hl&&n.includes(hl))n=n.replace(new RegExp(\`(\${hl})\`,'gi'),'<mark>$1</mark>');
    const b=col.pk?'pk':fkCols.has(col.name)?'fk':'x';
    const l=col.pk?'PK':fkCols.has(col.name)?'FK':'';
    return \`<div class="cr"><span class="bdg \${b}">\${l}</span><span class="cn">\${n}</span><span class="ct">\${col.type}</span></div>\`;
  }).join('');
  const fks=t.fks.length?\`<div class="fksec"><div class="fkt">Foreign Keys</div>\${
    t.fks.map(f=>\`<div class="fkr"><span>\${f.from_col}</span><span class="fka">→</span><span class="fkl" onclick="jump('\${f.to_table}')">\${f.to_table}.\${f.to_col}</span></div>\`).join('')
  }</div>\`:'';
  return \`<div class="tc \${hl&&name.includes(hl)?'hl':''}" id="tc-\${name}">
    <div class="tc-h" onclick="tog('\${name}')">
      <div style="width:8px;height:8px;border-radius:50%;background:\${c};flex-shrink:0"></div>
      <div class="tc-n">\${name}</div>
      <span class="tc-m">\${t.columns.length} cols</span>
      \${t.fks.length?\`<span class="tc-f">\${t.fks.length} FK</span>\`:''}
      <span class="tc-tg" id="tg-\${name}">▼</span>
    </div>
    <div class="tc-b" id="bd-\${name}"><div class="cols">\${cols}</div>\${fks}</div>
  </div>\`;
}

function tog(n){
  document.getElementById('bd-'+n)?.classList.toggle('open');
  document.getElementById('tg-'+n)?.classList.toggle('open');
}
function sel(d){
  dom=d;view='overview';q='';document.getElementById('si').value='';
  buildSB();buildMN();document.getElementById('mn').scrollTop=0;
}
function scrollToDomain(slug){
  // Sempre ir pra overview (com todos os domínios visíveis) antes de scrollar
  const needRebuild=view!=='overview'||dom!==null||q;
  if(needRebuild){
    dom=null;view='overview';q='';
    document.getElementById('si').value='';
    buildSB();buildMN();
  }
  setTimeout(()=>{
    const el=document.getElementById('dom-'+slug);
    if(el) el.scrollIntoView({behavior:'smooth',block:'start'});
  }, needRebuild?80:0);
}
function scrollToViews(){
  const needRebuild=view!=='overview'||dom!==null||q;
  if(needRebuild){
    dom=null;view='overview';q='';
    document.getElementById('si').value='';
    buildSB();buildMN();
  }
  setTimeout(()=>{
    const el=document.getElementById('dom-views');
    if(el) el.scrollIntoView({behavior:'smooth',block:'start'});
  }, needRebuild?80:0);
}
function selOverview(){
  dom=null;view='overview';q='';document.getElementById('si').value='';
  buildSB();buildMN();document.getElementById('mn').scrollTop=0;
}
function selInfra(){
  dom=null;view='infra';q='';document.getElementById('si').value='';
  buildSB();buildMN();document.getElementById('mn').scrollTop=0;
}
function jump(name){
  dom=T2D[name]||null;view='overview';q='';document.getElementById('si').value='';
  buildSB();buildMN();
  setTimeout(()=>{
    const el=document.getElementById('tc-'+name);
    if(!el)return;
    el.scrollIntoView({behavior:'smooth',block:'center'});
    const bd=document.getElementById('bd-'+name);
    const tg=document.getElementById('tg-'+name);
    if(bd&&!bd.classList.contains('open')){bd.classList.add('open');tg?.classList.add('open');}
    el.style.borderColor='#6366f1';
    setTimeout(()=>el.style.borderColor='',2000);
  },80);
}
let st;
function search(v){
  clearTimeout(st);q=v.trim();
  st=setTimeout(()=>{dom=null;view='overview';buildSB();buildMN();},200);
}
buildSB();buildMN();
</script>
</body>
</html>`;
}

// ---------------------------------------------------------------------------
// 5. Execução
// ---------------------------------------------------------------------------
console.log('\n═══ AgenciaPsi — Dashboard Generator ═══\n');

const { schemaPath, date, available } = resolveSchema();
console.log(`  → Schema:    ${schemaPath}`);
if (available.length > 1) console.log(`  → Outros backups: ${available.slice(1).join(', ')}`);

const content = fs.readFileSync(schemaPath, 'utf8');
console.log(`  → Lendo schema... (${(content.length / 1024).toFixed(0)} KB)`);

const { tables, views } = parseSchema(content);
const domains = buildDomains(tables);
const totalFKs = Object.values(tables).reduce((a, t) => a + t.fks.length, 0);

console.log(`  → ${Object.keys(tables).length} tabelas · ${totalFKs} FKs · ${views.length} views`);

// Avisa sobre tabelas novas não mapeadas
if (domains['Outros']) {
    console.log(`\n  ⚠  Tabelas novas sem domínio definido (aparecerão em "Outros"):`);
    domains['Outros'].forEach((t) => console.log(`     - ${t}`));
    console.log(`  → Edite "domains" em db.config.json para mapeá-las.\n`);
}

// Infra stats
const infraGroups = Object.keys(INFRASTRUCTURE).length;
const infraItems = Object.values(INFRASTRUCTURE).reduce((a, g) => a + (g.items?.length || 0), 0);
console.log(`  → Infraestrutura: ${infraGroups} grupos, ${infraItems} itens`);

const html = generateHTML(tables, views, domains, date, available);
fs.writeFileSync(OUTPUT_FILE, html, 'utf8');

console.log(`\n✔ Gerado: ${OUTPUT_FILE}`);
console.log(`   Tamanho: ${(fs.statSync(OUTPUT_FILE).size / 1024).toFixed(0)} KB`);
console.log(`   Abra no browser: file://${OUTPUT_FILE.replace(/\\/g, '/')}\n`);
