const { useState, useEffect, useCallback, useRef, useMemo } = React;

const SCO_LOGO = "data:image/png;base64,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";
const SESI_LOGO = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAoAKADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD5UooooAKKKUUAFFb3h/RUvYmnuWbyg21VXgt+Nah0vRclN0YYcEefz/Ou+ll9WpBTukn3ZzTxUIS5dWcZRXQ2GjwXep3QRmFpC20EHJY+ma0pNM0WNikhjVx1DTEEfrRTy+pOPNdJebCeKhF2s2zjaK6Wx0W1vLu5kVm+xxvsQK2d3HPPpV5tK0UMUJjDDgjz+f504ZbVmua6S9RSxcIu1mcZRXd+DtE8P3er6s+tzrHpdpGu1mn2ZckdCOT0PArA8YjRl1ll8OJOtiqKMzZyzdyM846V5jnatKjZ3W76febRqKTskYVFbNnYW89nE0hETcliTlm/3RnGMdeM8e9LHptp5nN0CitsJbAycA569Ov5VZoYtFamoafDbW7OJHEgbbtfb1yQRgHOeM/QisugAooooAKKKKACiiigApRSUooA6bwzqsMMP2S5bZ8xKOenPY1d1fQoLpHmtgI58FsD7r1DaaTpUtrExkUuUG4rNjnHNWrzUrPTbHyoJFd1XaiK24/ia+ipQtQ5MTZxS011PKnL97zUb36jPCcXl6VvI++5P5cVHcWmiSyySyzozsSzfv8Aqak8PXls+mRwGREkQFWVmwep5H51U1HStLt7KaRH/eKpKjzQcn6UO31aHKoySXUP+X0uZtNvoQ+GtVgtke2uDsVn3Ix6DPY1parokF6rS2+I5yMgj7rfX/GqthpWlz2cLPIplKAuVmxz9KvT6hZaVZCKGRXZBhI1bcc+5pUY/uOXE2cbaa6hUl+85qN+bqUPCdjE0Ek80StIJNqlhnGBzisLWJfO1S6fqDIQPoOP6V1OiXENto0ZeaIPhpCC4znJPSuOhUXF0iu4QSOAWPQZPWuLGcsMPTpx66v+vmdNC8qs5P0Nf+zbWRIyZEtxtGcsCynHVsnpkcYx1pYdNtDsLSshDHdl1BXB4zz3x29ajvdPtUgVopCHwq4LqcMTyD7459KkGnWDTsRMTGr8oHXleDkH0APNeWdhiSsGkcrnBJIzzTK3hplpuUPJgBwPldRlS2M5yecdvSmrp1kyN+9YbTz865A25P4DOPUmgDDooooAKKKKACiiigAooooAWjNFFABRmiigAozRRQAZpKKKACiiigAooooAKKKKACiiigD/2Q==";


/* ═══ SUPABASE CONFIG ═══ */
const SUPABASE_URL = "https://bfswivvfjtkmwkwwpufy.supabase.co";
const SUPABASE_KEY = "sb_publishable_IZGR7J3K9nxwZVwhZlK25A_DqD6spFK";
let SESSION_ID = null; // dynamically set from LIVE session or picker

async function supaFetch(table, params = "") {
  try {
    const sep = params ? "&" : "";
    const url = `${SUPABASE_URL}/rest/v1/${table}?${params}${sep}apikey=${SUPABASE_KEY}`;
    const r = await fetch(url, {
      headers: {
        "apikey": SUPABASE_KEY,
        "Authorization": `Bearer ${SUPABASE_KEY}`,
        "Content-Type": "application/json",
      },
    });
    if (!r.ok) { console.error("supaFetch error:", r.status, await r.text()); return []; }
    return r.json();
  } catch (e) { console.error("supaFetch exception:", e); return []; }
}

function processLiveInsights(raw) {
  const counts = {}, sentsMap = {}, lastQuote = {}, gThemes = {}, gEdges = {}, sT = {proposta:0,duvida:0,tensao:0};
  const sentsByTheme = {};    // { themeId: { proposta: N, duvida: N, tensao: N } }
  const groupsByTheme = {};   // { themeId: [groupId, ...] }
  const questionByTheme = {}; // { themeId: "pergunta..." }
  const trendByTheme = {};    // { themeId: "up" | "stable" | "down" }
  const insights = raw.map(r => ({
    group: r.group_id, theme: r.theme, sentiment: r.sentiment,
    text: r.summary, ts: new Date(r.created_at).getTime(),
    question: r.facilitator_question,
    qaQuestionId: r.qa_question_id || null,
    verbatim: r.verbatim || null,
    participantId: r.participant_id || null,
  }));
  raw.forEach(r => {
    counts[r.theme] = (counts[r.theme] || 0) + 1;
    sentsMap[r.theme] = r.sentiment;
    lastQuote[r.theme] = r.summary;
    if (sT[r.sentiment] !== undefined) sT[r.sentiment]++;
    const gt = gThemes[r.group_id] || [];
    if (!gt.includes(r.theme)) gThemes[r.group_id] = [...gt, r.theme].slice(-5);
    const ge = gEdges[r.group_id] || [];
    gEdges[r.group_id] = [...ge, r.theme];
    // Sentiment breakdown per theme
    if (!sentsByTheme[r.theme]) sentsByTheme[r.theme] = {proposta:0,duvida:0,tensao:0};
    if (sentsByTheme[r.theme][r.sentiment] !== undefined) sentsByTheme[r.theme][r.sentiment]++;
    // Groups per theme (unique)
    if (!groupsByTheme[r.theme]) groupsByTheme[r.theme] = [];
    if (!groupsByTheme[r.theme].includes(r.group_id)) groupsByTheme[r.theme].push(r.group_id);
    // Latest facilitator question per theme
    if (r.facilitator_question) questionByTheme[r.theme] = r.facilitator_question;
  });
  // Trend: compare last 5min vs previous 5min
  const now = Date.now();
  Object.keys(counts).forEach(theme => {
    const themeInsights = raw.filter(r => r.theme === theme);
    const recent = themeInsights.filter(r => new Date(r.created_at).getTime() > now - 300000).length;
    const prior = themeInsights.filter(r => {
      const t = new Date(r.created_at).getTime();
      return t > now - 600000 && t <= now - 300000;
    }).length;
    trendByTheme[theme] = recent > prior * 1.5 ? "up" : recent < prior * 0.7 ? "down" : "stable";
  });
  return { counts, sentsMap, lastQuote, gThemes, gEdges, sT, insights, sentsByTheme, groupsByTheme, trendByTheme, questionByTheme };
}

function calcConvergence(gEdges, caseGroupMap) {
  const conv = {};
  Object.entries(caseGroupMap).forEach(([cid, gids]) => {
    const sets = gids.map(g => [...new Set(gEdges[g] || [])]);
    if (sets.every(s => s.length === 0)) { conv[parseInt(cid)] = 0; return; }
    let sim = 0, pairs = 0;
    for (let i=0;i<sets.length;i++) for (let j=i+1;j<sets.length;j++) {
      const a = new Set(sets[i]), b = new Set(sets[j]);
      const inter = [...a].filter(x => b.has(x)).length;
      const union = new Set([...a, ...b]).size;
      sim += union > 0 ? (inter/union)*100 : 0;
      pairs++;
    }
    conv[parseInt(cid)] = pairs > 0 ? Math.round(sim/pairs) : 0;
  });
  return conv;
}

/* ═══ DYNAMIC THEME UTILITIES ═══ */
const THEME_PALETTE = [
  "#FF4D4D", "#FF8A50", "#FFD93D", "#B388FF",
  "#448AFF", "#4DD9E8", "#3DFFA2", "#FF6B9D",
  "#00E5FF", "#FFAB40", "#69F0AE", "#EA80FC",
];

function themeColor(id) {
  let hash = 0;
  for (let i = 0; i < id.length; i++) hash = ((hash << 5) - hash) + id.charCodeAt(i);
  return THEME_PALETTE[Math.abs(hash) % THEME_PALETTE.length];
}

function themeLabel(id) {
  return id.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
}

function getTheme(id) {
  return { id, label: themeLabel(id), color: themeColor(id) };
}

// Dynamic groups loaded from activity_groups table — fallback to defaults
const DEFAULT_CASES = [
  { id: 1, name: "Metalúrgico", region: "Sul", color: "#FF4D4D" },
  { id: 2, name: "Têxtil", region: "NE", color: "#FFD93D" },
  { id: 3, name: "Gráfica", region: "CO", color: "#3DFFA2" },
];
const DEFAULT_GROUPS = Array.from({ length: 9 }, (_, i) => ({ id: i + 1, caseData: DEFAULT_CASES[Math.floor(i / 3)] }));

// These will be populated at runtime
let CASES = [...DEFAULT_CASES];
let GROUPS = [...DEFAULT_GROUPS];
let CASE_GROUP_MAP = {}; // { caseId: [groupNumber, ...] }

async function loadActivityGroups() {
  try {
    const data = await supaFetch("activity_groups", `session_id=eq.${SESSION_ID}&order=group_number.asc`);
    if (!Array.isArray(data) || data.length === 0) return;

    // Build CASES from unique case_ids
    const caseMap = {};
    data.forEach(g => {
      if (g.case_id && !caseMap[g.case_id]) {
        caseMap[g.case_id] = { id: g.case_id, name: g.case_name || '', region: g.case_region || '', color: g.color || '#3DFFA2' };
      }
    });
    CASES = Object.values(caseMap);
    // If no case_ids but groups exist, derive legend entries from unique colors/names
    if (CASES.length === 0 && data.length > 0) {
      const colorKey = {};
      data.forEach(g => {
        const key = (g.case_name || '') + '|' + (g.color || '#3DFFA2');
        if (!colorKey[key]) colorKey[key] = { id: Object.keys(colorKey).length + 1, name: g.case_name || g.label, region: g.case_region || '', color: g.color || '#3DFFA2' };
      });
      CASES = Object.values(colorKey);
    }
    if (CASES.length === 0) CASES = [...DEFAULT_CASES];

    // Build GROUPS
    GROUPS = data.map(g => ({
      id: g.group_number,
      label: g.label,
      caseName: g.case_name || '',
      caseData: caseMap[g.case_id] || { id: 0, name: g.case_name || '', region: g.case_region || '', color: g.color || '#3DFFA2' },
    }));

    // Build case-to-groups map for convergence
    CASE_GROUP_MAP = {};
    data.forEach(g => {
      if (g.case_id) {
        if (!CASE_GROUP_MAP[g.case_id]) CASE_GROUP_MAP[g.case_id] = [];
        CASE_GROUP_MAP[g.case_id].push(g.group_number);
      }
    });

    console.log("[GROUPS] Loaded", GROUPS.length, "groups,", CASES.length, "cases from DB");
  } catch (e) {
    console.error("Failed to load activity groups:", e);
  }
}

// Fetch all sessions for the picker, auto-select the LIVE one
async function fetchSessions() {
  try {
    const data = await supaFetch("sessions", "select=id,name,client_name,status,is_live&order=created_at.desc");
    if (!Array.isArray(data)) return [];
    return data;
  } catch (e) { console.error("Failed to fetch sessions:", e); return []; }
}

async function initSession() {
  const sessions = await fetchSessions();
  // Check URL param first: ?session=<uuid>
  const urlSession = new URLSearchParams(window.location.search).get("session");
  if (urlSession && sessions.find(s => s.id === urlSession)) {
    SESSION_ID = urlSession;
  } else {
    const live = sessions.find(s => s.is_live);
    if (live) {
      SESSION_ID = live.id;
    } else if (sessions.length > 0) {
      SESSION_ID = sessions[0].id; // fallback to most recent
    }
  }
  if (SESSION_ID) await loadActivityGroups();
  return sessions;
}
const SENTS = {
  proposta: { color: "#3DFFA2", icon: "↑", label: "Proposta" },
  duvida: { color: "#FFD93D", icon: "?", label: "Dúvida" },
  tensao: { color: "#FF4D4D", icon: "!", label: "Tensão" },
};
const POOL = [
  { text: "Precisamos mapear os fluxos de encaminhamento entre os serviços", group: 2, theme: "fluxo_encaminhamento", sentiment: "proposta", question: "Se cada unidade desenha seu próprio fluxo, como garantir que o paciente não se perca entre eles?" },
  { text: "Falta integração entre os sistemas de informação disponíveis", group: 5, theme: "integracao_sistemas", sentiment: "tensao", question: "Quantas decisões erradas estão sendo tomadas agora por falta de dados integrados?" },
  { text: "Como medir o impacto real das ações preventivas?", group: 7, theme: "avaliacao_impacto", sentiment: "duvida", question: "Estamos medindo o que é fácil contar ou o que realmente importa para o trabalhador?" },
  { text: "Capacitação das equipes é pré-requisito para qualquer mudança", group: 1, theme: "capacitacao_equipes", sentiment: "proposta", question: "Investimos em capacitação pontual ou em transformação cultural permanente?" },
  { text: "Dados existem mas não são usados para tomada de decisão", group: 4, theme: "uso_dados", sentiment: "tensao", question: "O que impede que dados já disponíveis cheguem a quem decide?" },
  { text: "Protocolo de atendimento precisa ser padronizado entre unidades", group: 3, theme: "padronizacao_protocolo", sentiment: "proposta", question: "Padronizar significa uniformizar ou definir princípios que cada contexto adapta?" },
  { text: "Comunicação entre setores é o principal gargalo identificado", group: 8, theme: "comunicacao_intersetorial", sentiment: "tensao", question: "Se a comunicação é o gargalo, por que continuamos criando mais setores em vez de mais pontes?" },
  { text: "Qual o papel da tecnologia na modernização dos processos?", group: 6, theme: "tecnologia_processos", sentiment: "duvida", question: "A tecnologia está servindo quem opera o sistema ou quem o administra?" },
  { text: "Prevenção deveria ter mais investimento que remediação", group: 9, theme: "prevencao_investimento", sentiment: "proposta", question: "Qual seria o argumento que convenceria um gestor a redirecionar orçamento de tratamento para prevenção?" },
  { text: "Indicadores atuais não refletem a realidade do campo", group: 1, theme: "uso_dados", sentiment: "tensao", question: "O que impede que dados já disponíveis cheguem a quem decide?" },
  { text: "Parcerias com universidades podem acelerar a pesquisa aplicada", group: 3, theme: "capacitacao_equipes", sentiment: "proposta", question: "Investimos em capacitação pontual ou em transformação cultural permanente?" },
  { text: "Como garantir continuidade das ações entre gestões?", group: 6, theme: "padronizacao_protocolo", sentiment: "duvida", question: "Padronizar significa uniformizar ou definir princípios que cada contexto adapta?" },
  { text: "Escuta ativa dos profissionais da ponta é fundamental", group: 2, theme: "comunicacao_intersetorial", sentiment: "proposta", question: "Se a comunicação é o gargalo, por que continuamos criando mais setores em vez de mais pontes?" },
  { text: "Regionalização das estratégias é necessária — realidades são distintas", group: 5, theme: "fluxo_encaminhamento", sentiment: "tensao", question: "Se cada unidade desenha seu próprio fluxo, como garantir que o paciente não se perca entre eles?" },
  { text: "Falta clareza nos papéis e responsabilidades de cada ator", group: 4, theme: "integracao_sistemas", sentiment: "tensao", question: "Quantas decisões erradas estão sendo tomadas agora por falta de dados integrados?" },
  { text: "Precisamos de um sistema de monitoramento em tempo real", group: 7, theme: "avaliacao_impacto", sentiment: "proposta", question: "Estamos medindo o que é fácil contar ou o que realmente importa para o trabalhador?" },
  { text: "A articulação com a rede pública ainda é muito frágil", group: 8, theme: "tecnologia_processos", sentiment: "tensao", question: "A tecnologia está servindo quem opera o sistema ou quem o administra?" },
  { text: "Modelo de atenção precisa considerar determinantes sociais", group: 9, theme: "prevencao_investimento", sentiment: "duvida", question: "Qual seria o argumento que convenceria um gestor a redirecionar orçamento de tratamento para prevenção?" },
];

/* ═══ MISSION DATA ═══ */
const CONTENT_TYPES = {
  proposal: { color: "#3DFFA2", icon: "▸", label: "Proposta" },
  example: { color: "#448AFF", icon: "◆", label: "Exemplo" },
  objection: { color: "#FF4D4D", icon: "✕", label: "Objeção" },
  addition: { color: "#FFD93D", icon: "+", label: "Adição" },
};

const MISSION_POOL = [
  { group: 1, section_id: "criterios_acionamento", section_label: "Critérios de Acionamento do SUS", content: "O encaminhamento ao SUS deve ser acionado quando o DASS-21 indicar score severo (≥21 em depressão, ≥15 em ansiedade, ≥26 em estresse) e o trabalhador não possuir plano de saúde com cobertura psicológica.", content_type: "proposal" },
  { group: 2, section_id: "criterios_acionamento", section_label: "Critérios de Acionamento do SUS", content: "Além dos scores, deve-se considerar fatores situacionais: ideação suicida, abuso de substâncias ativo, ou impacto funcional significativo (absenteísmo >5 dias/mês).", content_type: "addition" },
  { group: 3, section_id: "criterios_acionamento", section_label: "Critérios de Acionamento do SUS", content: "Exemplo concreto: trabalhador da metalúrgica com DASS-21 severo, sem plano, empresa em cidade pequena com apenas 1 UBS — como garantir atendimento em tempo hábil?", content_type: "example" },
  { group: 4, section_id: "fluxo_encaminhamento", section_label: "Fluxo de Encaminhamento Prático", content: "O fluxo ideal deve seguir: triagem SESI → avaliação de gravidade → contato prévio com UBS/CAPS (encaminhamento quente) → envio de relatório padronizado → acompanhamento em 15 dias.", content_type: "proposal" },
  { group: 5, section_id: "fluxo_encaminhamento", section_label: "Fluxo de Encaminhamento Prático", content: "Nem sempre o encaminhamento quente é possível — em regiões com alta rotatividade de profissionais do SUS, o contato pessoal se perde. Precisamos de um canal institucional, não pessoal.", content_type: "objection" },
  { group: 6, section_id: "fluxo_encaminhamento", section_label: "Fluxo de Encaminhamento Prático", content: "Checklist mínimo para encaminhamento: histórico ocupacional, resultado Mentis/ASSTI, CID provisório, gravidade estimada, contato do profissional SESI responsável.", content_type: "addition" },
  { group: 7, section_id: "contrarreferencia", section_label: "Contrarreferência e Acompanhamento", content: "O retorno da informação do SUS para o SESI deve incluir: confirmação de atendimento, diagnóstico (quando autorizado), plano terapêutico resumido, e previsão de alta ou reavaliação.", content_type: "proposal" },
  { group: 8, section_id: "contrarreferencia", section_label: "Contrarreferência e Acompanhamento", content: "Na prática, a contrarreferência quase nunca acontece. Sugestão: criar formulário digital simplificado que o profissional do SUS preencha em 2 minutos, acessível por QR code.", content_type: "proposal" },
  { group: 9, section_id: "contrarreferencia", section_label: "Contrarreferência e Acompanhamento", content: "Caso real no CO: trabalhador encaminhado ao CAPS nunca teve retorno registrado. 3 meses depois, crise no trabalho. Se houvesse acompanhamento, poderia ter sido evitada.", content_type: "example" },
  { group: 1, section_id: "papel_sesmt", section_label: "Papel do SESMT e da Empresa", content: "O SESMT deve ser o elo entre o trabalhador e o sistema de saúde, não apenas registrar atestados. Proposta: incluir acompanhamento de saúde mental nas atribuições formais.", content_type: "proposal" },
  { group: 4, section_id: "papel_sesmt", section_label: "Papel do SESMT e da Empresa", content: "Em micro e pequenas empresas não existe SESMT. Quem faz esse papel? O SESI precisa preencher essa lacuna com equipe volante ou teleconsulta.", content_type: "objection" },
  { group: 7, section_id: "indicadores_monitoramento", section_label: "Indicadores de Monitoramento", content: "Indicadores mínimos: taxa de encaminhamento/mês, tempo médio até primeiro atendimento SUS, taxa de contrarreferência, taxa de retorno ao trabalho pós-tratamento.", content_type: "proposal" },
  { group: 2, section_id: "fluxo_encaminhamento", section_label: "Fluxo de Encaminhamento Prático", content: "O modelo de Belo Horizonte funciona: parceria formal SESI-SMS com canal dedicado. Reduz tempo de espera de 45 para 12 dias em média.", content_type: "example" },
  { group: 5, section_id: "criterios_acionamento", section_label: "Critérios de Acionamento do SUS", content: "Cuidado com critérios rígidos demais — um trabalhador pode ter score moderado no DASS-21 mas estar em situação de risco real. O julgamento clínico deve prevalecer.", content_type: "objection" },
  { group: 8, section_id: "indicadores_monitoramento", section_label: "Indicadores de Monitoramento", content: "Adicionar indicador qualitativo: satisfação do trabalhador com o processo de encaminhamento, coletado por survey anônimo 30 dias após.", content_type: "addition" },
];

function processMissionOutputs(raw) {
  return raw.map(r => ({
    group: r.group_id,
    section_id: r.section_id,
    section_label: r.section_label,
    content: r.content,
    content_type: r.content_type || "proposal",
    ts: new Date(r.created_at).getTime(),
  }));
}

/* ═══ THEME-GROUP COLOR BLENDING ═══ */
function blendGroupColors(themeId, groupThemeEdges, groups) {
  const groupWeights = {};
  Object.entries(groupThemeEdges).forEach(([gid, themes]) => {
    const count = themes.filter(t => t === themeId).length;
    if (count > 0) groupWeights[gid] = count;
  });
  if (Object.keys(groupWeights).length === 0) return null;
  let r = 0, g = 0, b = 0, totalWeight = 0;
  Object.entries(groupWeights).forEach(([gid, weight]) => {
    const group = groups.find(gr => gr.id === parseInt(gid));
    const hex = group?.caseData?.color || "#3DFFA2";
    r += parseInt(hex.slice(1, 3), 16) * weight;
    g += parseInt(hex.slice(3, 5), 16) * weight;
    b += parseInt(hex.slice(5, 7), 16) * weight;
    totalWeight += weight;
  });
  r = Math.round(r / totalWeight);
  g = Math.round(g / totalWeight);
  b = Math.round(b / totalWeight);
  return "#" + [r, g, b].map(c => c.toString(16).padStart(2, "0")).join("");
}

/* ═══ GRAFOS VIEW ═══ */
function GrafosView({ groupThemeEdges, themeCounts, convergenceData, onNodeClick, groups, groupsVersion }) {
  const canvasRef = useRef(null);
  const nodesRef = useRef(null);
  const raf = useRef(null);
  const hovered = useRef(null);
  const sizeRef = useRef({ w: 0, h: 0 });

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const resize = () => {
      const rect = canvas.parentElement.getBoundingClientRect();
      sizeRef.current = { w: rect.width, h: rect.height };
      canvas.width = rect.width * 2;
      canvas.height = rect.height * 2;
      canvas.style.width = rect.width + "px";
      canvas.style.height = rect.height + "px";
    };
    resize();
    window.addEventListener("resize", resize);

    // Initialize nodes: groups only (theme nodes added dynamically as insights arrive)
    if (!nodesRef.current) {
      // Use actual canvas size; if not ready yet, use fallback dimensions
      const W = sizeRef.current.w || 1200;
      const H = sizeRef.current.h || 700;
      const uniqueCases = [...new Set(GROUPS.map(g => g.caseData?.id).filter(Boolean))];
      const caseCount = Math.max(uniqueCases.length, 1);
      const groupNodes = GROUPS.map((g, i) => {
        const caseIdx = uniqueCases.indexOf(g.caseData?.id);
        // Spread cases evenly across the full width with generous margins
        const cx = W * (0.15 + (caseIdx >= 0 ? caseIdx : i) * (0.7 / caseCount));
        const cy = H * 0.45;
        const groupsInCase = GROUPS.filter(og => og.caseData?.id === g.caseData?.id);
        const idxInCase = groupsInCase.indexOf(g);
        const angle = (idxInCase / Math.max(groupsInCase.length, 1)) * Math.PI * 2 + Math.random() * 0.3;
        // Spread groups within each case with generous radius
        const spread = Math.min(W, H) * 0.15;
        return {
          id: "g" + g.id, type: "group", group: g,
          x: cx + Math.cos(angle) * spread + (Math.random() - 0.5) * 40,
          y: cy + Math.sin(angle) * spread + (Math.random() - 0.5) * 40,
          vx: 0, vy: 0, radius: 40,
          color: g.caseData?.color || "#3DFFA2", label: g.label || ("G" + g.id),
          sublabel: g.caseName || g.caseData?.name || "",
        };
      });
      nodesRef.current = [...groupNodes];
    }

    const handleMove = (e) => {
      const rect = canvas.getBoundingClientRect();
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;
      const nodes = nodesRef.current || [];
      let found = null;
      for (const n of nodes) {
        const dx = n.x - mx, dy = n.y - my;
        if (Math.sqrt(dx*dx + dy*dy) < n.radius + 6) { found = n.id; break; }
      }
      hovered.current = found;
      canvas.style.cursor = found ? "pointer" : "default";
    };
    canvas.addEventListener("mousemove", handleMove);
    const handleClick = (e) => {
      const rect = canvas.getBoundingClientRect();
      const mx = e.clientX - rect.left;
      const my = e.clientY - rect.top;
      const nodes = nodesRef.current || [];
      for (const n of nodes) {
        const dx = n.x - mx, dy = n.y - my;
        if (Math.sqrt(dx*dx + dy*dy) < n.radius + 6) {
          if (n.type === "group") {
            onNodeClick({type:"group", id: n.group.id});
          } else {
            onNodeClick({type:"theme", id: n.theme.id});
          }
          return;
        }
      }
    };
    canvas.addEventListener("click", handleClick);

    return () => {
      window.removeEventListener("resize", resize);
      canvas.removeEventListener("mousemove", handleMove);
      canvas.removeEventListener("click", handleClick);
      cancelAnimationFrame(raf.current);
    };
  }, []);

  // Sync group nodes when groups prop updates — update existing + add missing ones
  useEffect(() => {
    const nodes = nodesRef.current;
    if (!nodes || !groups || groups.length === 0) return;
    const W = sizeRef.current.w || 1200;
    const H = sizeRef.current.h || 700;
    const existingGroupIds = new Set(nodes.filter(n => n.type === "group").map(n => n.group.id));
    // Update existing group nodes
    nodes.forEach(n => {
      if (n.type === "group") {
        const g = groups.find(g => g.id === n.group.id);
        if (g) {
          n.label = g.label || ("G" + g.id);
          n.sublabel = g.caseName || g.caseData?.name || "";
          n.color = g.caseData?.color || "#3DFFA2";
          n.group = g;
        }
      }
    });
    // Add any missing group nodes
    const uniqueCases = [...new Set(groups.map(g => g.caseData?.id).filter(Boolean))];
    const caseCount = Math.max(uniqueCases.length, 1);
    groups.forEach(g => {
      if (!existingGroupIds.has(g.id)) {
        const caseIdx = uniqueCases.indexOf(g.caseData?.id);
        const cx = W * (0.15 + (caseIdx >= 0 ? caseIdx : 0) * (0.7 / caseCount));
        const cy = H * 0.45;
        const groupsInCase = groups.filter(og => og.caseData?.id === g.caseData?.id);
        const idxInCase = groupsInCase.indexOf(g);
        const angle = (idxInCase / Math.max(groupsInCase.length, 1)) * Math.PI * 2 + Math.random() * 0.3;
        const spread = Math.min(W, H) * 0.15;
        nodes.push({
          id: "g" + g.id, type: "group", group: g,
          x: cx + Math.cos(angle) * spread + (Math.random() - 0.5) * 40,
          y: cy + Math.sin(angle) * spread + (Math.random() - 0.5) * 40,
          vx: 0, vy: 0, radius: 40,
          color: g.caseData?.color || "#3DFFA2", label: g.label || ("G" + g.id),
          sublabel: g.caseName || g.caseData?.name || "",
        });
      }
    });
  }, [groups, groupsVersion]);

  useEffect(() => {
    const nodes = nodesRef.current;
    if (!nodes) return;

    // Add new theme nodes for any themes not yet in the graph
    const W0 = sizeRef.current.w || 1200;
    const H0 = sizeRef.current.h || 700;
    const existingThemeIds = new Set(nodes.filter(n => n.type === "theme").map(n => n.theme.id));
    Object.keys(themeCounts).forEach(id => {
      if (!existingThemeIds.has(id)) {
        const t = getTheme(id);
        const angle = Math.random() * Math.PI * 2;
        nodes.push({
          id: "t_" + t.id, type: "theme", theme: t,
          x: W0 * 0.5 + Math.cos(angle) * (Math.min(W0, H0) * 0.4) + (Math.random() - 0.5) * 120,
          y: H0 * 0.5 + Math.sin(angle) * (Math.min(W0, H0) * 0.38) + (Math.random() - 0.5) * 100,
          vx: 0, vy: 0, radius: 14,
          color: t.color, label: t.label,
          birthTime: performance.now(),
          displayRadius: 0, targetRadius: 10, prevTarget: 0,
        });
      }
    });

    // Update theme node sizes based on counts (smooth interpolation)
    nodes.forEach(n => {
      if (n.type === "theme") {
        const count = themeCounts[n.theme.id] || 0;
        const newTarget = Math.max(18, Math.min(60, 18 + count * 3));
        if (n.targetRadius !== undefined && newTarget > (n.prevTarget || 0)) {
          n.pulseStart = performance.now();
        }
        n.prevTarget = n.targetRadius || newTarget;
        n.targetRadius = newTarget;
        if (n.displayRadius === undefined) n.displayRadius = 0;
        n.radius = n.targetRadius; // keep for physics calculations
        n.count = count;
      }
    });

    // Blend theme node colors from contributing groups
    nodes.forEach(n => {
      if (n.type === "theme") {
        const blended = blendGroupColors(n.theme.id, groupThemeEdges, groups || GROUPS);
        if (blended) n.color = blended;
      }
    });

    // Build edge list
    const edges = [];
    Object.entries(groupThemeEdges).forEach(([gid, themes]) => {
      themes.forEach(tid => {
        const strength = (groupThemeEdges[gid]?.filter(x => x === tid).length) || 1;
        edges.push({ from: "g" + gid, to: "t_" + tid, strength: Math.min(strength, 5) });
      });
    });

    // Convergence: same-case groups attract each other proportionally
    const caseGroups = {};
    GROUPS.forEach(g => {
      const cid = g.caseData?.id || 0;
      (caseGroups[cid] = caseGroups[cid] || []).push("g" + g.id);
    });

    function frame() {
      const { w: W, h: H } = sizeRef.current;
      const canvas = canvasRef.current;
      if (!canvas || !W) { raf.current = requestAnimationFrame(frame); return; }
      const ctx = canvas.getContext("2d");
      ctx.setTransform(2, 0, 0, 2, 0, 0);
      ctx.clearRect(0, 0, W, H);

      const damping = 0.85;
      const nodeMap = {};
      nodes.forEach(n => nodeMap[n.id] = n);

      // Physics: repulsion between all nodes — scales with node count
      const nodeScale = Math.max(1, nodes.length / 10); // stronger when more nodes
      for (let i = 0; i < nodes.length; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
          const a = nodes[i], b = nodes[j];
          const dx = a.x - b.x, dy = a.y - b.y;
          const dist = Math.sqrt(dx * dx + dy * dy) || 1;
          const minDist = a.radius + b.radius + 100;
          const bothGroups = a.type === "group" && b.type === "group";
          const repStrength = (bothGroups ? 8000 : 4000) * nodeScale;
          if (dist < minDist * 6) {
            const force = repStrength / (dist * dist + 100);
            a.vx += (dx / dist) * force;
            a.vy += (dy / dist) * force;
            b.vx -= (dx / dist) * force;
            b.vy -= (dy / dist) * force;
          }
        }
      }

      // Physics: edge attraction — gentler, longer target distance
      edges.forEach(e => {
        const a = nodeMap[e.from], b = nodeMap[e.to];
        if (!a || !b) return;
        const dx = b.x - a.x, dy = b.y - a.y;
        const dist = Math.sqrt(dx * dx + dy * dy) || 1;
        const target = 250 + (5 - e.strength) * 40;
        const force = (dist - target) * 0.001 * e.strength;
        a.vx += (dx / dist) * force;
        a.vy += (dy / dist) * force;
        b.vx -= (dx / dist) * force * 0.3;
        b.vy -= (dy / dist) * force * 0.3;
      });

      // Convergence: same-case groups attract (very gently)
      Object.entries(caseGroups).forEach(([cid, gids]) => {
        const conv = (convergenceData[parseInt(cid)] || 15) / 100;
        const attract = conv * 0.004;
        for (let i = 0; i < gids.length; i++) {
          for (let j = i + 1; j < gids.length; j++) {
            const a = nodeMap[gids[i]], b = nodeMap[gids[j]];
            if (!a || !b) continue;
            const dx = b.x - a.x, dy = b.y - a.y;
            const dist = Math.sqrt(dx * dx + dy * dy) || 1;
            a.vx += (dx / dist) * attract * dist;
            a.vy += (dy / dist) * attract * dist;
            b.vx -= (dx / dist) * attract * dist;
            b.vy -= (dy / dist) * attract * dist;
          }
        }
      });

      // Minimal center gravity — just prevent drift off-screen
      nodes.forEach(n => {
        n.vx += (W * 0.5 - n.x) * 0.0002;
        n.vy += (H * 0.5 - n.y) * 0.0002;
        n.vx *= damping;
        n.vy *= damping;
        n.x += n.vx;
        n.y += n.vy;
        n.x = Math.max(n.radius + 20, Math.min(W - n.radius - 20, n.x));
        n.y = Math.max(n.radius + 20, Math.min(H - n.radius - 20, n.y));
      });

      // Interpolate display radii for smooth growth
      const now = performance.now();
      nodes.forEach(n => {
        if (n.type === "theme" && n.displayRadius !== undefined) {
          n.displayRadius += (n.targetRadius - n.displayRadius) * 0.08;
        }
      });

      // Draw edges
      const hovId = hovered.current;
      edges.forEach(e => {
        const a = nodeMap[e.from], b = nodeMap[e.to];
        if (!a || !b) return;
        const isHighlighted = hovId && (hovId === e.from || hovId === e.to);
        const alpha = isHighlighted ? 0.5 : (hovId ? 0.04 : 0.15);
        ctx.beginPath();
        ctx.moveTo(a.x, a.y);
        ctx.lineTo(b.x, b.y);
        ctx.strokeStyle = a.color + (isHighlighted ? "88" : Math.round(alpha * 255).toString(16).padStart(2, "0"));
        ctx.lineWidth = isHighlighted ? e.strength * 1.2 : Math.max(0.5, e.strength * 0.6);
        ctx.stroke();
      });

      // Draw convergence arcs between same-case groups
      Object.entries(caseGroups).forEach(([cid, gids]) => {
        const conv = Math.round(convergenceData[parseInt(cid)] || 15);
        const caseColor = CASES.find(c => c.id === parseInt(cid) || c.id === cid)?.color || "#fff";
        const members = gids.map(id => nodeMap[id]).filter(Boolean);
        if (members.length < 2) return;
        // Draw dashed arcs between same-case members
        for (let i = 0; i < members.length; i++) {
          for (let j = i + 1; j < members.length; j++) {
            ctx.beginPath();
            ctx.setLineDash([4, 6]);
            ctx.moveTo(members[i].x, members[i].y);
            ctx.lineTo(members[j].x, members[j].y);
            ctx.strokeStyle = caseColor + (conv > 50 ? "40" : "18");
            ctx.lineWidth = 1;
            ctx.stroke();
            ctx.setLineDash([]);
          }
        }
        // Convergence label at centroid
        const cx = members.reduce((s, m) => s + m.x, 0) / members.length;
        const cy = members.reduce((s, m) => s + m.y, 0) / members.length;
        const col = conv > 70 ? "#3DFFA2" : conv > 40 ? "#FFD93D" : "#FF4D4D";

        ctx.save();
        // Glass pill background
        ctx.fillStyle = "rgba(0,0,0,0.55)";
        const tw = 70, th = 36;
        ctx.beginPath();
        ctx.roundRect(cx - tw/2, cy - th/2 - 12, tw, th, 6);
        ctx.fill();
        ctx.strokeStyle = col + "44";
        ctx.lineWidth = 1;
        ctx.stroke();

        ctx.font = "800 18px monospace";
        ctx.fillStyle = col;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText(conv + "%", cx, cy - 8);
        ctx.font = "600 9px sans-serif";
        ctx.fillStyle = "#ccc";
        ctx.fillText((CASES.find(c => c.id === parseInt(cid) || c.id === cid)?.name || "").toUpperCase(), cx, cy + 8);
        ctx.restore();
      });

      // Draw nodes — themes first, then groups on top so they're always visible
      const themeNodes = nodes.filter(n => n.type === "theme");
      const groupNodes = nodes.filter(n => n.type === "group");
      [...themeNodes, ...groupNodes].forEach(n => {
        const isHov = hovId === n.id;
        const isConnected = hovId && edges.some(e => (e.from === hovId && e.to === n.id) || (e.to === hovId && e.from === n.id));
        const dimmed = hovId && !isHov && !isConnected && hovId !== n.id;
        const alpha = dimmed ? 0.15 : 1;

        if (n.type === "group") {
          // Outer ring
          ctx.beginPath();
          ctx.arc(n.x, n.y, n.radius + 5, 0, Math.PI * 2);
          ctx.strokeStyle = n.color + (isHov ? "66" : "22");
          ctx.lineWidth = isHov ? 2 : 1;
          ctx.stroke();

          // Main circle
          const grad = ctx.createRadialGradient(n.x - 4, n.y - 4, 0, n.x, n.y, n.radius);
          grad.addColorStop(0, n.color + (dimmed ? "30" : "DD"));
          grad.addColorStop(1, n.color + (dimmed ? "15" : "88"));
          ctx.beginPath();
          ctx.arc(n.x, n.y, n.radius, 0, Math.PI * 2);
          ctx.fillStyle = grad;
          ctx.fill();

          // Label
          ctx.font = "800 18px monospace";
          ctx.fillStyle = dimmed ? "rgba(0,0,0,0.3)" : "rgba(0,0,0,0.9)";
          ctx.textAlign = "center";
          ctx.textBaseline = "middle";
          ctx.fillText(n.label, n.x, n.y);

          // Sublabel — always visible, bolder on hover
          if (!dimmed) {
            ctx.font = (isHov ? "700 13px" : "600 11px") + " sans-serif";
            ctx.fillStyle = isHov ? "#fff" : "#ccc";
            ctx.fillText(n.sublabel, n.x, n.y + n.radius + (isHov ? 18 : 16));
          }

        } else {
          // Theme node — use displayRadius for smooth growth
          const r = n.displayRadius !== undefined ? n.displayRadius : n.radius;
          if (r < 1) return;

          // Birth animation — expanding ring
          if (n.birthTime && (now - n.birthTime) < 1500) {
            const progress = (now - n.birthTime) / 1500;
            const ringR = r + 25 * progress;
            const ringAlpha = Math.round((1 - progress) * 128);
            ctx.beginPath();
            ctx.arc(n.x, n.y, ringR, 0, Math.PI * 2);
            ctx.strokeStyle = n.color + ringAlpha.toString(16).padStart(2, "0");
            ctx.lineWidth = 2.5 * (1 - progress);
            ctx.stroke();
          }

          // Pulse ring on count increase
          if (n.pulseStart && (now - n.pulseStart) < 800) {
            const p = (now - n.pulseStart) / 800;
            ctx.beginPath();
            ctx.arc(n.x, n.y, r + 18 * p, 0, Math.PI * 2);
            const pulseAlpha = Math.round((1 - p) * 80);
            ctx.strokeStyle = n.color + pulseAlpha.toString(16).padStart(2, "0");
            ctx.lineWidth = 1.5 * (1 - p);
            ctx.stroke();
          }

          // Glow for active themes
          if (n.count > 0 && !dimmed) {
            ctx.beginPath();
            ctx.arc(n.x, n.y, r + 8, 0, Math.PI * 2);
            ctx.fillStyle = n.color + "08";
            ctx.fill();
          }

          // Main circle
          const grad = ctx.createRadialGradient(n.x - r * 0.2, n.y - r * 0.2, 0, n.x, n.y, Math.max(r, 1));
          grad.addColorStop(0, n.color + (dimmed ? "20" : "CC"));
          grad.addColorStop(1, n.color + (dimmed ? "08" : "55"));
          ctx.beginPath();
          ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
          ctx.fillStyle = grad;
          ctx.fill();

          // Border ring
          ctx.beginPath();
          ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
          ctx.strokeStyle = n.color + (dimmed ? "10" : "44");
          ctx.lineWidth = 1;
          ctx.stroke();

          // Count inside if big enough
          if (r > 20 && n.count && !dimmed) {
            ctx.font = "800 " + Math.max(14, r * 0.55) + "px monospace";
            ctx.fillStyle = "#fff";
            ctx.textAlign = "center";
            ctx.textBaseline = "middle";
            ctx.fillText(n.count, n.x, n.y);
          }

          // Label below
          if ((r > 14 && !dimmed) || isHov) {
            ctx.font = (isHov ? "700" : "600") + " " + (isHov ? "14" : "12") + "px sans-serif";
            ctx.fillStyle = dimmed ? "#444" : "#ddd";
            ctx.textAlign = "center";
            ctx.fillText(n.label, n.x, n.y + r + 16);
          }
        }
      });

      // Legend
      ctx.save();
      const legendH = 36 + CASES.length * 22;
      ctx.fillStyle = "rgba(0,0,0,0.5)";
      ctx.beginPath();
      ctx.roundRect(16, H - legendH - 16, 250, legendH, 8);
      ctx.fill();
      ctx.font = "700 10px sans-serif";
      ctx.fillStyle = "#ccc";
      ctx.textAlign = "left";
      ctx.fillText("LEGENDA", 30, H - legendH + 6);

      ctx.font = "500 11px sans-serif";
      CASES.forEach((c, i) => {
        const y = H - legendH + 26 + i * 22;
        ctx.beginPath();
        ctx.arc(38, y, 7, 0, Math.PI * 2);
        ctx.fillStyle = c.color + "CC";
        ctx.fill();
        ctx.fillStyle = "#ddd";
        ctx.textAlign = "left";
        ctx.fillText((c.name || "Case " + c.id) + (c.region ? " (" + c.region + ")" : ""), 52, y + 4);
      });

      // Right legend: node types
      ctx.fillStyle = "rgba(0,0,0,0.5)";
      ctx.beginPath();
      ctx.roundRect(W - 260, H - 78, 244, 64, 8);
      ctx.fill();
      ctx.font = "500 11px sans-serif";
      ctx.beginPath();
      ctx.arc(W - 240, H - 54, 12, 0, Math.PI * 2);
      ctx.fillStyle = "#666";
      ctx.fill();
      ctx.fillStyle = "#ddd";
      ctx.fillText("Grupo (tamanho fixo)", W - 222, H - 50);

      ctx.beginPath();
      ctx.arc(W - 240, H - 30, 7, 0, Math.PI * 2);
      ctx.fillStyle = "#3DFFA2" + "88";
      ctx.fill();
      ctx.fillStyle = "#ddd";
      ctx.fillText("Tema (tamanho = menções)", W - 222, H - 26);
      ctx.restore();

      raf.current = requestAnimationFrame(frame);
    }
    raf.current = requestAnimationFrame(frame);
    return () => cancelAnimationFrame(raf.current);
  }, [groupThemeEdges, themeCounts, convergenceData]);

  return <canvas ref={canvasRef} style={{ width: "100%", height: "100%", display: "block" }} />;
}

/* ═══ WAITING ROOM ═══ */
function WaitingRoom({ activeCount }) {
  const canvasRef = useRef(null);
  const particlesRef = useRef(null);
  const raf = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const parent = canvas.parentElement;
    const resize = () => {
      const rect = parent.getBoundingClientRect();
      canvas.width = rect.width * 2;
      canvas.height = rect.height * 2;
      canvas.style.width = rect.width + "px";
      canvas.style.height = rect.height + "px";
    };
    resize();
    window.addEventListener("resize", resize);

    if (!particlesRef.current) {
      particlesRef.current = Array.from({ length: 54 }, (_, i) => ({
        baseAngle: (i / 54) * Math.PI * 2 + (Math.random() - 0.5) * 0.4,
        baseRadius: 100 + Math.random() * 120,
        phase: Math.random() * Math.PI * 2,
        speed: 0.0002 + Math.random() * 0.0004,
        size: 2 + Math.random() * 2,
      }));
    }

    function frame(t) {
      const W = canvas.width / 2, H = canvas.height / 2;
      const ctx = canvas.getContext("2d");
      ctx.setTransform(2, 0, 0, 2, 0, 0);
      ctx.clearRect(0, 0, W, H);
      const cx = W / 2, cy = H / 2;
      const particles = particlesRef.current;

      // Compute positions
      const positions = particles.map((p, i) => {
        const wobble = Math.sin(t * 0.001 + p.phase) * 8;
        const r = p.baseRadius + wobble;
        const angle = p.baseAngle + t * p.speed;
        return {
          x: cx + Math.cos(angle) * r,
          y: cy + Math.sin(angle) * r,
          active: i < activeCount,
          size: p.size,
          breath: 0.5 + 0.5 * Math.sin(t * 0.002 + p.phase),
        };
      });

      // Draw connections between active particles
      for (let i = 0; i < positions.length; i++) {
        if (!positions[i].active) continue;
        for (let j = i + 1; j < positions.length; j++) {
          if (!positions[j].active) continue;
          const dx = positions[i].x - positions[j].x;
          const dy = positions[i].y - positions[j].y;
          const dist = Math.sqrt(dx * dx + dy * dy);
          if (dist < 150) {
            const alpha = (1 - dist / 150) * 0.12;
            ctx.beginPath();
            ctx.moveTo(positions[i].x, positions[i].y);
            ctx.lineTo(positions[j].x, positions[j].y);
            ctx.strokeStyle = "rgba(61,255,162," + alpha + ")";
            ctx.lineWidth = 0.5;
            ctx.stroke();
          }
        }
      }

      // Draw particles
      positions.forEach(p => {
        if (p.active) {
          // Glow
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.size + 6, 0, Math.PI * 2);
          ctx.fillStyle = "rgba(61,255,162," + (0.04 + 0.03 * p.breath) + ")";
          ctx.fill();
          // Core
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
          ctx.fillStyle = "rgba(61,255,162," + (0.5 + 0.3 * p.breath) + ")";
          ctx.fill();
        } else {
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.size * 0.7, 0, Math.PI * 2);
          ctx.fillStyle = "rgba(80,80,80,0.25)";
          ctx.fill();
        }
      });

      // Central breathing text
      const textAlpha = 0.3 + 0.25 * Math.sin(t * 0.0015);
      ctx.font = "300 14px 'DM Sans', sans-serif";
      ctx.fillStyle = "rgba(255,255,255," + textAlpha + ")";
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      ctx.fillText("Aguardando participantes...", cx, cy - 12);

      // Participant counter
      if (activeCount > 0) {
        ctx.font = "800 28px 'DM Mono', monospace";
        ctx.fillStyle = "rgba(61,255,162," + (0.5 + 0.3 * Math.sin(t * 0.002)) + ")";
        ctx.fillText(activeCount, cx, cy + 20);
        ctx.font = "500 8px 'DM Sans', sans-serif";
        ctx.fillStyle = "rgba(255,255,255,0.3)";
        ctx.fillText("CONECTADOS", cx, cy + 38);
      }

      raf.current = requestAnimationFrame(frame);
    }
    raf.current = requestAnimationFrame(frame);
    return () => {
      window.removeEventListener("resize", resize);
      cancelAnimationFrame(raf.current);
    };
  }, [activeCount]);

  return <canvas ref={canvasRef} style={{ width: "100%", height: "100%", display: "block" }} />;
}

/* ═══ TREEMAP LAYOUT ═══ */
function computeTreemap(items, rect) {
  // items: [{id, count, ...}] sorted desc by count
  // rect: {x, y, w, h}
  // Returns [{...item, rect: {x, y, w, h}}]
  if (items.length === 0) return [];
  if (items.length === 1) return [{ ...items[0], rect }];

  const total = items.reduce((s, it) => s + it.count, 0);
  if (total === 0) return items.map((it, i) => ({ ...it, rect: { x: rect.x, y: rect.y + (rect.h / items.length) * i, w: rect.w, h: rect.h / items.length } }));

  // Split into two groups: first group takes proportional area
  // Find the split point where the first group is closest to half the total
  let bestSplit = 1, bestDiff = Infinity;
  let running = 0;
  for (let i = 0; i < items.length - 1; i++) {
    running += items[i].count;
    const diff = Math.abs(running / total - 0.5);
    if (diff < bestDiff) { bestDiff = diff; bestSplit = i + 1; }
  }

  const left = items.slice(0, bestSplit);
  const right = items.slice(bestSplit);
  const leftTotal = left.reduce((s, it) => s + it.count, 0);
  const ratio = leftTotal / total;

  // Split along the longer axis
  if (rect.w >= rect.h) {
    const lw = rect.w * ratio;
    return [
      ...computeTreemap(left, { x: rect.x, y: rect.y, w: lw, h: rect.h }),
      ...computeTreemap(right, { x: rect.x + lw, y: rect.y, w: rect.w - lw, h: rect.h }),
    ];
  } else {
    const lh = rect.h * ratio;
    return [
      ...computeTreemap(left, { x: rect.x, y: rect.y, w: rect.w, h: lh }),
      ...computeTreemap(right, { x: rect.x, y: rect.y + lh, w: rect.w, h: rect.h - lh }),
    ];
  }
}

/* ═══ TILE ═══ */
function Tile({ theme, count, sentiment, lastInsight, groupData, isGroup, hit, rect, totalArea, onClick, isNew, isGenesis, milestone, sentBreakdown, themeGroups, trend, question }) {
  const base = isGroup ? groupData.caseData.color : (theme?.color||"#555");
  const anim = isGenesis ? "genesis 1.2s cubic-bezier(0.16,1,0.3,1)"
             : isNew ? "crystallize 0.8s cubic-bezier(0.16,1,0.3,1)"
             : hit ? "tileHit 0.6s cubic-bezier(0.34,1.56,0.64,1)"
             : "none";
  // Compute proportional size ratio from area
  const areaRatio = rect ? (rect.w * rect.h) / (totalArea || 1) : 0.5;
  const hr = Math.sqrt(areaRatio); // sqrt to make font scaling less extreme
  const isBig = hr >= 0.4;
  const trendArrow = trend === "up" ? "↑" : trend === "down" ? "↓" : "→";
  const trendColor = trend === "up" ? "#3DFFA2" : trend === "down" ? "#FF4D4D" : "#666";
  // Scale fonts proportionally based on area ratio
  const fLabel = Math.round(14 + hr * 18);   // 14–32px
  const fCount = Math.round(20 + hr * 44);   // 20–64px
  const fMencoes = Math.round(9 + hr * 7);   // 9–16px
  const fTrend = Math.round(12 + hr * 12);   // 12–24px
  // Sentiment bar percentages
  const sb = sentBreakdown || {proposta:0,duvida:0,tensao:0};
  const sbTotal = sb.proposta + sb.duvida + sb.tensao;
  const gap = 3;
  return (
    <div onClick={onClick} style={{
      position: "absolute",
      left: rect ? rect.x + gap/2 : 0,
      top: rect ? rect.y + gap/2 : 0,
      width: rect ? rect.w - gap : "100%",
      height: rect ? rect.h - gap : "100%",
      background: "linear-gradient(135deg, "+base+(hit?"38":"1A")+", "+base+(hit?"14":"08")+")",
      borderRadius: 3, padding: (isBig ? "16px 18px" : "10px 12px"),
      display:"flex", flexDirection:"column", justifyContent:"space-between",
      overflow:"hidden", boxSizing:"border-box",
      border: "1px solid "+base+(hit?"50":"20"),
      transition: "left 0.6s, top 0.6s, width 0.6s, height 0.6s, background 0.6s",
      cursor: "pointer",
      animation: anim,
    }}>
      <div style={{ position:"absolute",left:0,top:0,bottom:0,width:3,background:base+(hit?"CC":"40"),transition:"background 0.8s ease",boxShadow:hit?"0 0 12px "+base+"60":"none" }}/>
      <div style={{ position:"absolute",top:-14,right:-14,width:48,height:48,borderRadius:"50%",border:"1px solid "+base+(hit?"40":"18"),transition:"all 1s ease",pointerEvents:"none" }}/>
      {!isGroup && milestone && (
        <div style={{position:"absolute",top:8,right:8,background:base+"30",border:"1px solid "+base+"60",
          borderRadius:10,padding:"3px 10px",fontSize:13,fontWeight:800,color:base,fontFamily:"monospace",
          animation:"milestoneIn 0.5s cubic-bezier(0.34,1.56,0.64,1), milestoneFade 3s ease-out forwards",zIndex:2
        }}>{milestone}+</div>
      )}
      {isGroup ? (<>
        <div style={{ display:"flex",justifyContent:"space-between",alignItems:"center" }}>
          <span style={{ fontSize:16,fontWeight:700,color:base,letterSpacing:2,fontFamily:"monospace" }}>{groupData.label || ("G"+groupData.id)}</span>
          <span style={{ fontSize:13,color:"#ccc",textTransform:"uppercase",letterSpacing:1.5 }}>{groupData.caseData?.name ? "Caso " + groupData.caseData.id + " · " + (groupData.caseData.region||"") : ""}</span>
        </div>
        <div style={{ fontSize:14,color:"#ddd",marginTop:2,fontWeight:500 }}>{groupData.caseData?.name || ""}</div>
        <div style={{ display:"flex",gap:5,marginTop:8,flexWrap:"wrap" }}>
          {(groupData.activeThemes||[]).slice(0,4).map((tid,i) => {
            const th = getTheme(tid);
            return <span key={i} style={{ fontSize:12,padding:"2px 8px",borderRadius:2,background:th.color+"20",color:th.color,fontWeight:700,letterSpacing:0.5,textTransform:"uppercase" }}>{th.label.split(" ")[0]}</span>;
          })}
        </div>
      </>) : (<>
        <div>
          {/* Header: label + trend */}
          <div style={{ display:"flex",justifyContent:"space-between",alignItems:"flex-start" }}>
            <span style={{ fontSize:fLabel,fontWeight:600,color:"#fff",lineHeight:1.25,letterSpacing:-0.3,flex:1 }}>{theme.label}</span>
            <span style={{ fontSize:fTrend,color:trendColor,fontWeight:800,flexShrink:0,marginLeft:6 }}>{trendArrow}</span>
          </div>
          {/* Count + menções inline */}
          <div style={{ display:"flex",alignItems:"baseline",gap:6,marginTop:4 }}>
            <span style={{ fontSize:fCount,fontWeight:800,color:base,fontFamily:"monospace",lineHeight:1,letterSpacing:-2 }}>{count}</span>
            <span style={{ fontSize:fMencoes,color:"#ccc",fontWeight:700,textTransform:"uppercase",letterSpacing:2 }}>menções</span>
          </div>
          {/* Sentiment bar + group badges inline */}
          <div style={{ display:"flex",alignItems:"center",gap:8,marginTop:6 }}>
            {sbTotal > 0 && (
              <div style={{ display:"flex",height:4,flex:1,gap:1,borderRadius:2,overflow:"hidden" }}>
                {sb.proposta > 0 && <div style={{flex:sb.proposta,background:SENTS.proposta.color,borderRadius:1}} />}
                {sb.duvida > 0 && <div style={{flex:sb.duvida,background:SENTS.duvida.color,borderRadius:1}} />}
                {sb.tensao > 0 && <div style={{flex:sb.tensao,background:SENTS.tensao.color,borderRadius:1}} />}
              </div>
            )}
            {themeGroups && themeGroups.length > 0 && (
              <div style={{ display:"flex",gap:3,flexShrink:0,alignItems:"center" }}>
                {themeGroups.slice(0,5).map(gid => {
                  const g = GROUPS.find(g=>g.id===gid);
                  const gc = g?.caseData?.color || "#888";
                  return <div key={gid} title={g?.label||("G"+gid)} style={{width:8,height:8,borderRadius:"50%",background:gc,border:"1px solid "+gc+"60"}} />;
                })}
                {themeGroups.length > 5 && <span style={{fontSize:11,color:"#999",fontFamily:"monospace"}}>+{themeGroups.length-5}</span>}
              </div>
            )}
          </div>
        </div>
        {/* Last citation (big tiles) */}
        {lastInsight && isBig && (
          <div style={{ fontSize:13,color:"#ddd",lineHeight:1.3,marginTop:"auto",borderTop:"1px solid rgba(255,255,255,0.05)",paddingTop:6,fontStyle:"italic",overflow:"hidden",display:"-webkit-box",WebkitLineClamp:2,WebkitBoxOrient:"vertical" }}>
            \"{lastInsight}\"
          </div>
        )}
      </>)}
    </div>
  );
}


/* ═══ DETAIL PANEL ═══ */
function DetailPanel({ selection, insights, onClose, questionByTheme }) {
  if (!selection) return null;
  const isGroup = selection.type === "group";
  const filtered = insights.filter(ins => 
    isGroup ? ins.group === selection.id : ins.theme === selection.id
  ).reverse();
  
  const grp = GROUPS.find(g=>g.id===selection.id);
  const title = isGroup
    ? (grp?.label || "G" + selection.id) + " — " + (grp?.caseData?.name || "")
    : getTheme(selection.id).label;
  const color = isGroup
    ? grp?.caseData?.color || "#fff"
    : getTheme(selection.id).color;

  return (
    <div style={{
      position:"fixed", top:0, right:0, bottom:0, width:420, zIndex:200,
      background:"linear-gradient(180deg, rgba(12,12,18,0.92), rgba(8,8,14,0.96))",
      backdropFilter:"blur(32px) saturate(180%)",
      WebkitBackdropFilter:"blur(32px) saturate(180%)",
      borderLeft:"1px solid rgba(255,255,255,0.08)",
      display:"flex", flexDirection:"column",
      animation:"panelIn 0.3s ease-out",
      boxShadow:"-20px 0 60px rgba(0,0,0,0.5)",
    }}>
      {/* Header */}
      <div style={{
        padding:"20px 24px 16px", 
        borderBottom:"1px solid rgba(255,255,255,0.06)",
      }}>
        <div style={{display:"flex",justifyContent:"space-between",alignItems:"flex-start"}}>
          <div>
            <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:2,fontWeight:700,marginBottom:6}}>
              {isGroup ? "Contribuições do grupo" : "Discussões sobre"}
            </div>
            <div style={{fontSize:18,fontWeight:700,color:color,lineHeight:1.2}}>
              {title}
            </div>
          </div>
          <div 
            onClick={onClose}
            style={{
              width:32,height:32,borderRadius:"50%",cursor:"pointer",
              border:"1px solid rgba(255,255,255,0.1)",
              display:"flex",alignItems:"center",justifyContent:"center",
              fontSize:14,color:"#999",
              background:"rgba(255,255,255,0.04)",
            }}
          >×</div>
        </div>
        <div style={{display:"flex",gap:16,marginTop:12}}>
          <div style={{textAlign:"center"}}>
            <div style={{fontSize:20,fontWeight:800,color:color,fontFamily:"monospace"}}>{filtered.length}</div>
            <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:1.5}}>menções</div>
          </div>
          {!isGroup && (
            <>
              <div style={{width:1,background:"rgba(255,255,255,0.06)"}} />
              <div style={{textAlign:"center"}}>
                <div style={{fontSize:20,fontWeight:800,color:"#3DFFA2",fontFamily:"monospace"}}>
                  {filtered.filter(i=>i.sentiment==="proposta").length}
                </div>
                <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:1.5}}>propostas</div>
              </div>
              <div style={{textAlign:"center"}}>
                <div style={{fontSize:20,fontWeight:800,color:"#FFD93D",fontFamily:"monospace"}}>
                  {filtered.filter(i=>i.sentiment==="duvida").length}
                </div>
                <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:1.5}}>dúvidas</div>
              </div>
              <div style={{textAlign:"center"}}>
                <div style={{fontSize:20,fontWeight:800,color:"#FF4D4D",fontFamily:"monospace"}}>
                  {filtered.filter(i=>i.sentiment==="tensao").length}
                </div>
                <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:1.5}}>tensões</div>
              </div>
            </>
          )}
          {isGroup && (
            <>
              <div style={{width:1,background:"rgba(255,255,255,0.06)"}} />
              <div style={{textAlign:"center"}}>
                <div style={{fontSize:20,fontWeight:800,color:"#ddd",fontFamily:"monospace"}}>
                  {[...new Set(filtered.map(i=>i.theme))].length}
                </div>
                <div style={{fontSize:10,color:"#bbb",textTransform:"uppercase",letterSpacing:1.5}}>temas</div>
              </div>
            </>
          )}
        </div>
        {/* Participating groups (for theme) or themes (for group) */}
        <div style={{display:"flex",gap:4,marginTop:12,flexWrap:"wrap"}}>
          {isGroup ? (
            [...new Set(filtered.map(i=>i.theme))].map(tid => {
              const th = getTheme(tid);
              return <span key={tid} style={{fontSize:11,padding:"3px 8px",borderRadius:2,background:th.color+"20",color:th.color,fontWeight:700,letterSpacing:0.5,textTransform:"uppercase"}}>{th.label}</span>;
            })
          ) : (
            [...new Set(filtered.map(i=>i.group))].map(gid => {
              const g = GROUPS.find(g=>g.id===gid);
              return <span key={gid} style={{fontSize:11,padding:"3px 8px",borderRadius:2,background:(g?.caseData?.color||"#555")+"20",color:g?.caseData?.color||"#ddd",fontWeight:700,letterSpacing:0.5}}>{g?.label || ("G"+gid)}</span>;
            })
          )}
        </div>
        {/* Facilitator question (theme panels only) */}
        {!isGroup && questionByTheme && questionByTheme[selection.id] && (
          <div style={{margin:"12px 0 0",padding:"12px 16px",background:"rgba(61,255,162,0.06)",borderRadius:4,borderLeft:"3px solid "+color+"80"}}>
            <div style={{fontSize:10,color:"#999",textTransform:"uppercase",letterSpacing:2,fontWeight:700,marginBottom:4}}>Pergunta para o grupo</div>
            <div style={{fontSize:13,color:"#fff",fontStyle:"italic",lineHeight:1.5,fontWeight:500}}>{questionByTheme[selection.id]}</div>
          </div>
        )}
      </div>

      {/* Insight list */}
      <div style={{flex:1,overflowY:"auto",padding:"12px 24px"}}>
        {filtered.length === 0 ? (
          <div style={{textAlign:"center",padding:"40px 0",color:"#666",fontSize:14}}>
            Nenhuma interação ainda
          </div>
        ) : filtered.map((ins, i) => {
          const th = getTheme(ins.theme);
          const se = SENTS[ins.sentiment];
          return (
            <div key={ins.ts||i} style={{
              padding:"12px 0",
              borderBottom:"1px solid rgba(255,255,255,0.04)",
            }}>
              <div style={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:6}}>
                <div style={{display:"flex",alignItems:"center",gap:6}}>
                  <span style={{
                    fontSize:13,width:20,height:20,borderRadius:2,
                    background:se?.color+"18",color:se?.color,
                    display:"flex",alignItems:"center",justifyContent:"center",
                    fontWeight:800,
                  }}>{se?.icon}</span>
                  {isGroup ? (
                    <span style={{fontSize:12,color:th.color,fontWeight:700,textTransform:"uppercase",letterSpacing:1}}>{th.label}</span>
                  ) : (
                    <span style={{fontSize:12,color:GROUPS.find(g=>g.id===ins.group)?.caseData?.color||"#ddd",fontWeight:700,letterSpacing:1}}>{GROUPS.find(g=>g.id===ins.group)?.label || ("G"+ins.group)}</span>
                  )}
                </div>
                <span style={{fontSize:11,color:"#777",fontFamily:"monospace"}}>{se?.label}</span>
              </div>
              <p style={{fontSize:14,color:"#ddd",lineHeight:1.5,margin:0}}>
                {ins.text}
              </p>
            </div>
          );
        })}
      </div>

      {/* Bottom fade */}
      <div style={{
        height:40,
        background:"linear-gradient(transparent, rgba(8,8,14,0.9))",
        pointerEvents:"none",position:"absolute",bottom:0,left:0,right:0,
      }} />
    </div>
  );
}

/* ═══ TICKER BAR ═══ */
function TickerBar({ insights }) {
  const [idx, setIdx] = useState(0);
  const prevLen = useRef(0);

  // Auto-advance every 5s
  useEffect(() => {
    if (insights.length === 0) return;
    const iv = setInterval(() => {
      setIdx(p => (p + 1) % insights.length);
    }, 5000);
    return () => clearInterval(iv);
  }, [insights.length]);

  // Snap to latest when new insight arrives
  useEffect(() => {
    if (insights.length > prevLen.current && prevLen.current > 0) {
      setIdx(insights.length - 1);
    }
    prevLen.current = insights.length;
  }, [insights.length]);

  if (insights.length === 0) return null;
  const ins = insights[Math.min(idx, insights.length - 1)];
  const th = getTheme(ins.theme);
  const se = SENTS[ins.sentiment];
  const g = GROUPS.find(g => g.id === ins.group);

  return (
    <div style={{
      position:"fixed", bottom:0, left:0, right:0, height:64, zIndex:100,
      background:"linear-gradient(180deg, rgba(10,10,15,0.8), rgba(10,10,15,0.95))",
      backdropFilter:"blur(20px) saturate(160%)", WebkitBackdropFilter:"blur(20px) saturate(160%)",
      borderTop:"1px solid rgba(255,255,255,0.08)",
      display:"flex", alignItems:"center", padding:"0 32px", gap:18,
      pointerEvents:"none",
    }}>
      <div key={ins.ts || idx} style={{
        display:"flex", alignItems:"center", gap:16, flex:1, minWidth:0,
        animation:"tickerSlide 0.4s cubic-bezier(0.16,1,0.3,1)",
      }}>
        {/* Sentiment icon */}
        <span style={{fontSize:22, color:se?.color, fontWeight:800, flexShrink:0}}>{se?.icon}</span>
        {/* Theme label */}
        <span style={{
          fontSize:13, fontWeight:700, color:th.color, textTransform:"uppercase",
          letterSpacing:2, flexShrink:0, fontFamily:"'DM Mono',monospace",
        }}>{th.label}</span>
        {/* Divider */}
        <div style={{width:1, height:24, background:"rgba(255,255,255,0.15)", flexShrink:0}} />
        {/* Summary text */}
        <span style={{
          fontSize:15, color:"rgba(255,255,255,0.85)", lineHeight:1.4,
          whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis", flex:1,
        }}>{ins.text}</span>
      </div>
      {/* Group badge */}
      <div style={{
        display:"flex", alignItems:"center", gap:7, flexShrink:0,
      }}>
        <div style={{width:10, height:10, borderRadius:"50%", background:g?.caseData?.color||"#888"}} />
        <span style={{fontSize:13, color:"#999", fontFamily:"'DM Mono',monospace", letterSpacing:1}}>{g?.label || ("G"+ins.group)}</span>
      </div>
      {/* Progress dots */}
      <div style={{display:"flex", gap:4, flexShrink:0, marginLeft:8}}>
        {insights.slice(-5).map((_, i) => (
          <div key={i} style={{
            width:5, height:5, borderRadius:"50%",
            background: i === Math.min(idx, 4) ? "#3DFFA2" : "rgba(255,255,255,0.15)",
            transition:"background 0.3s ease",
          }} />
        ))}
      </div>
    </div>
  );
}

/* ═══ MISSÃO VIEW ═══ */
function MissaoView({ outputs, filterGroup, groups, cases, onSelectGroup }) {
  const [expandedGroup, setExpandedGroup] = useState(null);
  const prevOutputCount = useRef(0);
  const [flashGroups, setFlashGroups] = useState({});

  // Detect new outputs for flash animation
  useEffect(() => {
    if (outputs.length > prevOutputCount.current && prevOutputCount.current > 0) {
      const newest = outputs[outputs.length - 1];
      setFlashGroups(p => ({...p, [newest.group]: true}));
      setTimeout(() => setFlashGroups(p => { const n = {...p}; delete n[newest.group]; return n; }), 2000);
    }
    prevOutputCount.current = outputs.length;
  }, [outputs.length]);

  // Group outputs by group_id → section_id
  const grouped = useMemo(() => {
    const map = {};
    outputs.forEach(o => {
      if (!map[o.group]) map[o.group] = {};
      if (!map[o.group][o.section_id]) map[o.group][o.section_id] = { label: o.section_label, items: [] };
      map[o.group][o.section_id].items.push(o);
    });
    return map;
  }, [outputs]);

  // All unique sections across all groups
  const allSections = useMemo(() => {
    const seen = {};
    outputs.forEach(o => { seen[o.section_id] = o.section_label; });
    return Object.entries(seen).map(([id, label]) => ({ id, label }));
  }, [outputs]);

  // Per-table view: single group expanded
  if (filterGroup !== null) {
    const g = groups.find(g => g.id === filterGroup);
    const gColor = g?.caseData?.color || "#3DFFA2";
    const gOutputs = grouped[filterGroup] || {};
    return (
      <div style={{ flex:1, overflow:"auto", padding:"20px 32px 80px" }}>
        {/* Group header */}
        <div style={{ display:"flex", alignItems:"center", gap:16, marginBottom:24 }}>
          <div style={{
            width:48, height:48, borderRadius:8, background:gColor+"20", border:"2px solid "+gColor,
            display:"flex", alignItems:"center", justifyContent:"center",
            fontSize:20, fontWeight:800, color:gColor, fontFamily:"'DM Mono',monospace",
          }}>{g?.label || ("G"+filterGroup)}</div>
          <div>
            <div style={{ fontSize:20, fontWeight:700, color:"#fff" }}>{g?.caseData?.name || "Grupo "+filterGroup}</div>
            <div style={{ fontSize:13, color:"#999", letterSpacing:1, textTransform:"uppercase" }}>
              {g?.caseData?.region ? "Caso "+g.caseData.id+" · "+g.caseData.region : ""}
              {" · "}{Object.keys(gOutputs).length} seções · {outputs.filter(o=>o.group===filterGroup).length} contribuições
            </div>
          </div>
        </div>

        {/* Sections */}
        {allSections.length === 0 ? (
          <div style={{
            textAlign:"center", padding:"80px 0", color:"#555",
            animation:"breathe 3s ease infinite",
          }}>
            <div style={{ fontSize:32, marginBottom:12 }}>◇</div>
            <div style={{ fontSize:14, fontWeight:600 }}>Aguardando contribuições...</div>
            <div style={{ fontSize:13, color:"#444", marginTop:4 }}>As respostas do grupo aparecerão aqui em tempo real</div>
          </div>
        ) : (
          allSections.map(section => {
            const sData = gOutputs[section.id];
            const hasContent = sData && sData.items.length > 0;
            return (
              <div key={section.id} style={{
                marginBottom:16,
                background: hasContent ? "rgba(255,255,255,0.03)" : "rgba(255,255,255,0.01)",
                border: "1px solid "+(hasContent ? gColor+"30" : "rgba(255,255,255,0.06)"),
                borderRadius:6, overflow:"hidden",
                borderLeft: "3px solid "+(hasContent ? gColor : "rgba(255,255,255,0.1)"),
                animation: hasContent ? "none" : "breathe 4s ease infinite",
              }}>
                {/* Section header */}
                <div style={{
                  padding:"12px 16px", borderBottom: hasContent ? "1px solid rgba(255,255,255,0.06)" : "none",
                  display:"flex", justifyContent:"space-between", alignItems:"center",
                }}>
                  <div style={{ display:"flex", alignItems:"center", gap:10 }}>
                    <span style={{ fontSize:12, fontWeight:800, color:gColor, letterSpacing:2, textTransform:"uppercase", fontFamily:"'DM Mono',monospace" }}>
                      {section.label}
                    </span>
                    {hasContent && (
                      <span style={{ fontSize:11, color:"#999", fontFamily:"'DM Mono',monospace" }}>
                        {sData.items.length} {sData.items.length === 1 ? "contribuição" : "contribuições"}
                      </span>
                    )}
                  </div>
                  {hasContent && (
                    <div style={{ display:"flex", gap:4 }}>
                      {Object.entries(CONTENT_TYPES).map(([k,v]) => {
                        const ct = sData.items.filter(i => i.content_type === k).length;
                        return ct > 0 ? (
                          <span key={k} style={{ fontSize:10, padding:"2px 6px", borderRadius:8, background:v.color+"18", color:v.color, fontWeight:700 }}>{ct} {v.label}</span>
                        ) : null;
                      })}
                    </div>
                  )}
                </div>

                {/* Content items */}
                {hasContent ? sData.items.map((item, idx) => {
                  const ct = CONTENT_TYPES[item.content_type] || CONTENT_TYPES.proposal;
                  return (
                    <div key={idx} style={{
                      padding:"12px 16px", borderBottom:"1px solid rgba(255,255,255,0.03)",
                      animation:"slideIn 0.4s ease",
                      display:"flex", gap:12,
                    }}>
                      <div style={{
                        width:20, height:20, borderRadius:4, background:ct.color+"18",
                        display:"flex", alignItems:"center", justifyContent:"center",
                        fontSize:13, color:ct.color, fontWeight:800, flexShrink:0, marginTop:2,
                      }}>{ct.icon}</div>
                      <div style={{ flex:1 }}>
                        <div style={{ fontSize:13, color:"#e0e0e0", lineHeight:1.5 }}>{item.content}</div>
                        <div style={{ fontSize:11, color:"#666", marginTop:6, fontFamily:"'DM Mono',monospace" }}>
                          <span style={{ color:ct.color, fontWeight:700 }}>{ct.label}</span>
                          {item.ts && <span> · {new Date(item.ts).toLocaleTimeString("pt-BR", { hour:"2-digit", minute:"2-digit" })}</span>}
                        </div>
                      </div>
                    </div>
                  );
                }) : (
                  <div style={{ padding:"20px 16px", textAlign:"center", color:"#444", fontSize:13 }}>
                    Aguardando contribuições para esta seção...
                  </div>
                )}
              </div>
            );
          })
        )}
      </div>
    );
  }

  // Facilitator view: grid of all groups
  return (
    <div style={{ flex:1, overflow:"auto", padding:"12px 20px 80px" }}>
      {/* Section summary bar */}
      {allSections.length > 0 && (
        <div style={{ display:"flex", gap:8, marginBottom:12, flexWrap:"wrap", alignItems:"center" }}>
          <span style={{ fontSize:11, color:"#999", fontWeight:700, letterSpacing:2, textTransform:"uppercase", marginRight:4 }}>Seções</span>
          {allSections.map(s => {
            const total = outputs.filter(o => o.section_id === s.id).length;
            return (
              <span key={s.id} style={{
                fontSize:12, padding:"3px 10px", borderRadius:12,
                background:"rgba(255,255,255,0.05)", border:"1px solid rgba(255,255,255,0.08)",
                color:"#ccc", fontWeight:600,
              }}>
                {s.label} <span style={{ color:"#3DFFA2", fontFamily:"'DM Mono',monospace", marginLeft:4 }}>{total}</span>
              </span>
            );
          })}
        </div>
      )}

      {/* Group cards grid */}
      <div style={{
        display:"grid",
        gridTemplateColumns: groups.length <= 6 ? "repeat(3, 1fr)" : "repeat(3, 1fr)",
        gap:8,
      }}>
        {groups.map(g => {
          const gColor = g.caseData?.color || "#3DFFA2";
          const gOutputs = grouped[g.id] || {};
          const totalItems = outputs.filter(o => o.group === g.id).length;
          const sectionsActive = Object.keys(gOutputs).length;
          const isFlashing = flashGroups[g.id];
          const isExpanded = expandedGroup === g.id;

          return (
            <div key={g.id} onClick={() => setExpandedGroup(isExpanded ? null : g.id)} style={{
              background: "linear-gradient(135deg, "+gColor+(isFlashing?"28":"10")+", "+gColor+"05)",
              border: "1px solid "+gColor+(isFlashing?"50":"20"),
              borderLeft: "3px solid "+gColor+(isFlashing?"CC":"60"),
              borderRadius:4, padding:"14px 16px", cursor:"pointer",
              transition:"all 0.4s ease",
              gridColumn: isExpanded ? "span 3" : "span 1",
              animation: isFlashing ? "tileHit 0.6s cubic-bezier(0.34,1.56,0.64,1)" : "none",
            }}>
              {/* Card header */}
              <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:8 }}>
                <div style={{ display:"flex", alignItems:"center", gap:8 }}>
                  <span style={{ fontSize:14, fontWeight:800, color:gColor, fontFamily:"'DM Mono',monospace", letterSpacing:1 }}>
                    {g.label || ("G"+g.id)}
                  </span>
                  <span style={{ fontSize:12, color:"#999" }}>{g.caseData?.name || ""}</span>
                </div>
                <div style={{ display:"flex", alignItems:"center", gap:8 }}>
                  <span style={{ fontSize:20, fontWeight:800, color:gColor, fontFamily:"'DM Mono',monospace", lineHeight:1 }}>{totalItems}</span>
                  <span style={{ fontSize:10, color:"#888", textTransform:"uppercase", letterSpacing:1 }}>outputs</span>
                </div>
              </div>

              {/* Progress: sections with content / total sections */}
              {allSections.length > 0 && (
                <div style={{ display:"flex", gap:3, marginBottom:10 }}>
                  {allSections.map(s => {
                    const has = gOutputs[s.id] && gOutputs[s.id].items.length > 0;
                    return (
                      <div key={s.id} title={s.label} style={{
                        flex:1, height:4, borderRadius:2,
                        background: has ? gColor : "rgba(255,255,255,0.08)",
                        transition:"background 0.6s ease",
                      }} />
                    );
                  })}
                </div>
              )}

              {/* Section list */}
              {allSections.length === 0 ? (
                <div style={{ fontSize:13, color:"#555", fontStyle:"italic", animation:"breathe 4s ease infinite" }}>
                  Aguardando contribuições...
                </div>
              ) : (
                <div style={{ display:"flex", flexDirection:"column", gap:4 }}>
                  {allSections.map(s => {
                    const sData = gOutputs[s.id];
                    const hasItems = sData && sData.items.length > 0;
                    return (
                      <div key={s.id} style={{ display:"flex", alignItems:"flex-start", gap:6 }}>
                        <div style={{
                          width:6, height:6, borderRadius:1, marginTop:4, flexShrink:0,
                          background: hasItems ? gColor : "rgba(255,255,255,0.1)",
                        }} />
                        <div style={{ flex:1, minWidth:0 }}>
                          <span style={{
                            fontSize:12, fontWeight:700, color: hasItems ? "#ccc" : "#555",
                            letterSpacing:0.3,
                          }}>{s.label}</span>
                          {hasItems && !isExpanded && (
                            <div style={{
                              fontSize:13, color:"#888", marginTop:2,
                              overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap",
                            }}>
                              {sData.items[sData.items.length-1].content}
                            </div>
                          )}
                          {/* Expanded: show all items */}
                          {hasItems && isExpanded && sData.items.map((item, idx) => {
                            const ct = CONTENT_TYPES[item.content_type] || CONTENT_TYPES.proposal;
                            return (
                              <div key={idx} style={{
                                fontSize:13, color:"#ccc", lineHeight:1.4, marginTop:4,
                                padding:"6px 8px", background:"rgba(255,255,255,0.03)", borderRadius:3,
                                borderLeft:"2px solid "+ct.color+"60",
                              }}>
                                <span style={{ fontSize:10, color:ct.color, fontWeight:700, textTransform:"uppercase", letterSpacing:1, marginRight:6 }}>{ct.label}</span>
                                {item.content}
                              </div>
                            );
                          })}
                        </div>
                        {hasItems && (
                          <span style={{ fontSize:11, color:gColor, fontFamily:"'DM Mono',monospace", fontWeight:700, flexShrink:0 }}>
                            {sData.items.length}
                          </span>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ═══ P&R (PERGUNTA E RESPOSTA) VIEW ═══ */
function PerguntaRespostaView({ questions, insights, groups, cases, participants }) {
  const PR_ACCENT = "#FF6B6B";
  const SENT_COLORS = { proposta: "#3DFFA2", duvida: "#448AFF", tensao: "#FF4D4D" };

  // Group insights by qa_question_id
  const insightsByQuestion = useMemo(() => {
    const map = {};
    insights.forEach(i => {
      if (i.qaQuestionId) {
        if (!map[i.qaQuestionId]) map[i.qaQuestionId] = [];
        map[i.qaQuestionId].push(i);
      }
    });
    return map;
  }, [insights]);

  // Get group info helper
  const getGroupInfo = (groupId) => {
    const g = groups.find(g => g.id === groupId);
    return g || { id: groupId, label: "G" + groupId, caseData: { color: "#666" } };
  };

  // Get participant name helper
  const getParticipantName = (participantId, groupId) => {
    if (participantId && participants) {
      const p = participants.find(p => p.id === participantId);
      if (p) return p.display_name || "Anônimo";
    }
    return "Anônimo";
  };

  if (questions.length === 0) {
    return (
      <div style={{ flex:1, display:"flex", alignItems:"center", justifyContent:"center", flexDirection:"column", gap:16 }}>
        <div style={{ fontSize:40, opacity:0.3 }}>?</div>
        <div style={{ fontSize:16, color:"#555", fontWeight:600 }}>Modo P&R</div>
        <div style={{ fontSize:14, color:"#444", maxWidth:320, textAlign:"center", lineHeight:1.6 }}>
          Nenhuma pergunta cadastrada para esta sessão.<br/>
          Use a API admin para criar perguntas com a ação <span style={{fontFamily:"'DM Mono',monospace",color:"#888"}}>create_qa_question</span>.
        </div>
      </div>
    );
  }

  return (
    <div style={{
      flex:1, display:"flex", overflowX:"auto", overflowY:"hidden",
      padding:"20px 24px 80px", gap:16,
      scrollBehavior:"smooth",
    }}>
      {questions.map((q, qIdx) => {
        const qInsights = insightsByQuestion[q.id] || [];
        const isActive = q.is_active;
        const hasResponses = qInsights.length > 0;

        return (
          <div key={q.id} style={{
            minWidth:340, maxWidth:400, flexShrink:0,
            display:"flex", flexDirection:"column",
            background: isActive ? "rgba(255,107,107,0.04)" : "rgba(255,255,255,0.02)",
            border: "1px solid " + (isActive ? PR_ACCENT + "40" : "rgba(255,255,255,0.06)"),
            borderRadius:8, overflow:"hidden",
            borderTop: "3px solid " + (isActive ? PR_ACCENT : "rgba(255,255,255,0.1)"),
            animation: isActive ? "breathe 3s ease infinite" : "none",
          }}>
            {/* Column header */}
            <div style={{
              padding:"16px 16px 12px", borderBottom:"1px solid rgba(255,255,255,0.06)",
              background: isActive ? "rgba(255,107,107,0.06)" : "transparent",
            }}>
              <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:8 }}>
                <span style={{
                  fontSize:11, fontWeight:800, letterSpacing:2, textTransform:"uppercase",
                  fontFamily:"'DM Mono',monospace",
                  color: isActive ? PR_ACCENT : "#666",
                }}>
                  Pergunta {qIdx + 1}
                </span>
                <div style={{ display:"flex", gap:6, alignItems:"center" }}>
                  {isActive && (
                    <span style={{
                      fontSize:10, fontWeight:800, letterSpacing:1.5, textTransform:"uppercase",
                      padding:"2px 8px", borderRadius:8,
                      background: PR_ACCENT + "20", color: PR_ACCENT,
                      animation:"live 1.5s ease infinite",
                    }}>ATIVA</span>
                  )}
                  <span style={{
                    fontSize:11, fontFamily:"'DM Mono',monospace", color:"#888",
                  }}>{qInsights.length} resp.</span>
                </div>
              </div>
              <div style={{
                fontSize:14, fontWeight:600, color:"#e0e0e0", lineHeight:1.5,
              }}>{q.question_text}</div>
            </div>

            {/* AI Summary card (if generated) */}
            {q.summary && (
              <div style={{
                margin:"8px 8px 0", padding:"12px",
                background:"rgba(61,255,162,0.05)", border:"1px solid rgba(61,255,162,0.15)",
                borderRadius:6, borderLeft:"3px solid #3DFFA2",
              }}>
                <div style={{
                  fontSize:10, fontWeight:800, letterSpacing:2, textTransform:"uppercase",
                  color:"#3DFFA2", marginBottom:6, fontFamily:"'DM Mono',monospace",
                }}>RESUMO IA</div>
                <div style={{ fontSize:13, color:"#ccc", lineHeight:1.6 }}>{q.summary}</div>
              </div>
            )}

            {/* Response cards */}
            <div style={{
              flex:1, overflowY:"auto", padding:"8px",
              display:"flex", flexDirection:"column", gap:6,
            }}>
              {!hasResponses ? (
                <div style={{
                  flex:1, display:"flex", alignItems:"center", justifyContent:"center",
                  flexDirection:"column", gap:8, padding:"40px 16px",
                  color:"#444",
                }}>
                  <div style={{ fontSize:24, opacity:0.3 }}>{isActive ? "..." : "---"}</div>
                  <div style={{ fontSize:13, textAlign:"center" }}>
                    {isActive ? "Aguardando respostas dos participantes..." : "Nenhuma resposta registrada"}
                  </div>
                </div>
              ) : (
                qInsights.map((insight, idx) => {
                  const gInfo = getGroupInfo(insight.group);
                  const gColor = gInfo.caseData?.color || "#666";
                  const sentColor = SENT_COLORS[insight.sentiment] || "#888";
                  const participantName = getParticipantName(insight.participantId, insight.group);
                  return (
                    <div key={idx} style={{
                      padding:"10px 12px",
                      background:"rgba(255,255,255,0.03)",
                      border:"1px solid rgba(255,255,255,0.06)",
                      borderRadius:6,
                      borderLeft:"3px solid " + gColor,
                      animation:"slideIn 0.4s ease",
                    }}>
                      <div style={{ display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom:6 }}>
                        <div style={{ display:"flex", alignItems:"center", gap:6 }}>
                          <span style={{
                            fontSize:12, fontWeight:700, padding:"2px 6px", borderRadius:4,
                            background:gColor+"20", color:gColor,
                            fontFamily:"'DM Mono',monospace",
                          }}>{participantName}</span>
                          <div style={{
                            width:5, height:5, borderRadius:"50%", background:sentColor,
                          }} />
                        </div>
                        <span style={{
                          fontSize:10, color:"#666", fontFamily:"'DM Mono',monospace",
                        }}>{new Date(insight.ts).toLocaleTimeString("pt-BR", {hour:"2-digit",minute:"2-digit"})}</span>
                      </div>
                      <div style={{ fontSize:13, color:"#fff", lineHeight:1.5, fontStyle: insight.verbatim ? "normal" : "italic" }}>{insight.verbatim || insight.text}</div>
                    </div>
                  );
                })
              )}
            </div>

            {/* Footer with sentiment breakdown */}
            {hasResponses && (
              <div style={{
                padding:"8px 12px", borderTop:"1px solid rgba(255,255,255,0.06)",
                display:"flex", justifyContent:"space-between", alignItems:"center",
              }}>
                <div style={{ display:"flex", gap:8, alignItems:"center" }}>
                  {Object.entries(SENT_COLORS).map(([k,color]) => {
                    const ct = qInsights.filter(i => i.sentiment === k).length;
                    return ct > 0 ? (
                      <div key={k} style={{ display:"flex", alignItems:"center", gap:3 }}>
                        <div style={{ width:4, height:4, borderRadius:"50%", background:color }} />
                        <span style={{ fontSize:10, color:"#888", fontFamily:"'DM Mono',monospace" }}>{ct}</span>
                      </div>
                    ) : null;
                  })}
                </div>
                <span style={{ fontSize:10, color:"#666", fontFamily:"'DM Mono',monospace" }}>
                  {[...new Set(qInsights.map(i => i.group))].length} grupos
                </span>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

/* ═══ MAIN ═══ */
window.Dashboard = function Dashboard() {
  const initUrlParams = new URLSearchParams(window.location.search);
  const hasGroupFilter = initUrlParams.get("group") !== null;
  const [mode, setMode] = useState(hasGroupFilter ? "missao" : "mosaico");
  const [selection, setSelection] = useState(null);
  const [dataSource, setDataSource] = useState("demo"); // "demo" | "live"
  const prevInsightCount = useRef(0);
  const [insights, setInsights] = useState([]);
  const [counts, setCounts] = useState({});
  const [sentsMap, setSentsMap] = useState({});
  const [lastQuote, setLastQuote] = useState({});
  const [hits, setHits] = useState({});
  const [gThemes, setGThemes] = useState({});
  const [gEdges, setGEdges] = useState({});
  const [conv, setConv] = useState({});
  const [total, setTotal] = useState(0);
  const [active, setActive] = useState(0);
  const [sT, setST] = useState({proposta:0,duvida:0,tensao:0});
  const [sentsByTheme, setSentsByTheme] = useState({});
  const [groupsByTheme, setGroupsByTheme] = useState({});
  const [trendByTheme, setTrendByTheme] = useState({});
  const [questionByTheme, setQuestionByTheme] = useState({});
  const [allSessions, setAllSessions] = useState([]);
  const [currentSessionId, setCurrentSessionId] = useState(null);
  const [showQR, setShowQR] = useState(false);
  const [qrDataUrl, setQrDataUrl] = useState(null);
  const [menuOpen, setMenuOpen] = useState(false);
  const [clockTime, setClockTime] = useState(() => {
    const now = new Date();
    return now.toLocaleTimeString("pt-BR", { hour:"2-digit", minute:"2-digit", hour12:false });
  });
  const [sessionReady, setSessionReady] = useState(false);
  const isGenesisComplete = useRef(false);
  const [genesisActive, setGenesisActive] = useState(false);
  const [newThemes, setNewThemes] = useState({});
  const [milestones, setMilestones] = useState({});
  const prevCountsRef = useRef({});
  const [tickerIdx, setTickerIdx] = useState(0);
  const prevTickerLen = useRef(0);
  const [missionOutputs, setMissionOutputs] = useState([]);
  const [participantsList, setParticipantsList] = useState([]);
  const [qaQuestions, setQaQuestions] = useState([]);
  const [filterGroup, setFilterGroup] = useState(initUrlParams.get("group") ? parseInt(initUrlParams.get("group")) : null);
  const [groupsVersion, setGroupsVersion] = useState(0);
  const ix = useRef(0), tk = useRef(0);
  const missionIx = useRef(0);

  // Clock tick
  useEffect(() => {
    const id = setInterval(() => {
      const now = new Date();
      setClockTime(now.toLocaleTimeString("pt-BR", { hour:"2-digit", minute:"2-digit", hour12:false }));
    }, 1000);
    return () => clearInterval(id);
  }, []);

  // Ticker auto-advance
  useEffect(() => {
    if (insights.length === 0) return;
    const iv = setInterval(() => setTickerIdx(p => (p + 1) % insights.length), 5000);
    return () => clearInterval(iv);
  }, [insights.length]);

  // Snap to latest insight
  useEffect(() => {
    if (insights.length > prevTickerLen.current && prevTickerLen.current > 0) {
      setTickerIdx(insights.length - 1);
    }
    prevTickerLen.current = insights.length;
  }, [insights.length]);

  // Initialize: find LIVE session, load groups
  useEffect(() => {
    initSession().then(sessions => {
      setAllSessions(sessions);
      setCurrentSessionId(SESSION_ID);
      setSessionReady(true);
      setGroupsVersion(v => v + 1);
    });
  }, []);

  // Generate QR code when session changes
  useEffect(() => {
    const sid = currentSessionId || SESSION_ID;
    if (!sid) return;
    const chatUrl = `https://sessions-inky-five.vercel.app/?session=${sid}`;

    // Try QRCode library first, fall back to API
    if (typeof QRCode !== "undefined" && QRCode.toDataURL) {
      QRCode.toDataURL(chatUrl, { width: 300, margin: 2, color: { dark: "#000000", light: "#ffffff" } })
        .then(dataUrl => setQrDataUrl(dataUrl))
        .catch(err => {
          console.error("[QR] Library failed, using API fallback:", err);
          setQrDataUrl(`https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=${encodeURIComponent(chatUrl)}`);
        });
    } else {
      // Fallback: use free QR code API
      setQrDataUrl(`https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=${encodeURIComponent(chatUrl)}`);
    }
  }, [currentSessionId]);

  // When session changes via picker, update global and reload groups
  const switchSession = useCallback(async (newId) => {
    SESSION_ID = newId;
    setCurrentSessionId(newId);
    await loadActivityGroups();
    setGroupsVersion(v => v + 1);
    // Reset live data
    setInsights([]); setCounts({});
    setSentsMap({}); setLastQuote({}); setHits({}); setGThemes({});
    setGEdges({}); setConv({}); setTotal(0); setActive(0);
    setST({proposta:0,duvida:0,tensao:0}); setSentsByTheme({}); setGroupsByTheme({});
    setTrendByTheme({}); setQuestionByTheme({}); prevInsightCount.current=0;
    isGenesisComplete.current=false; setGenesisActive(false);
    setNewThemes({}); setMilestones({}); prevCountsRef.current={};
  }, []);

  const MILESTONE_THRESHOLDS = [5, 10, 20, 50];

  const detectBirthAndMilestones = useCallback((themeId, newCount) => {
    const prevCount = prevCountsRef.current[themeId] || 0;
    // Birth detection
    if (prevCount === 0 && newCount > 0) {
      if (!isGenesisComplete.current) {
        setGenesisActive(true);
        isGenesisComplete.current = true;
        setTimeout(() => setGenesisActive(false), 2000);
      }
      setNewThemes(p => ({...p, [themeId]: true}));
      setTimeout(() => setNewThemes(p => {const n={...p}; delete n[themeId]; return n;}), 1500);
    }
    // Milestone detection
    MILESTONE_THRESHOLDS.forEach(t => {
      if (newCount >= t && prevCount < t) {
        setMilestones(p => ({...p, [themeId]: t}));
        setTimeout(() => setMilestones(p => {const n={...p}; delete n[themeId]; return n;}), 3000);
      }
    });
  }, []);

  const tick = useCallback(() => {
    if (ix.current>=POOL.length) ix.current=0;
    const raw = POOL[ix.current]; ix.current++; tk.current++;
    const ins = {...raw, ts: Date.now()};
    setInsights(p=>[...p,ins]);
    setTotal(p=>p+Math.floor(Math.random()*4)+1);
    setActive(Math.min(54, 8+tk.current*3+Math.floor(Math.random()*5)));
    const bump = Math.floor(Math.random()*3)+1;
    const newCount = (prevCountsRef.current[ins.theme] || 0) + bump;
    detectBirthAndMilestones(ins.theme, newCount);
    prevCountsRef.current = {...prevCountsRef.current, [ins.theme]: newCount};
    setCounts(p=>({...p,[ins.theme]:(p[ins.theme]||0)+bump}));
    setSentsMap(p=>({...p,[ins.theme]:ins.sentiment}));
    setLastQuote(p=>({...p,[ins.theme]:ins.text}));
    setHits(p=>({...p,[ins.theme]:true,["g"+ins.group]:true}));
    setTimeout(()=>setHits(p=>{const n={...p};delete n[ins.theme];delete n["g"+ins.group];return n;}),3000);
    setGThemes(p=>{const e=p[ins.group]||[];return e.includes(ins.theme)?p:{...p,[ins.group]:[...e,ins.theme].slice(-5)};});
    setGEdges(p=>{const e=p[ins.group]||[];return {...p,[ins.group]:[...e,ins.theme]};});
    setST(p=>({...p,[ins.sentiment]:(p[ins.sentiment]||0)+1}));
    // Enriched data for tiles
    setSentsByTheme(p=>{const prev=p[ins.theme]||{proposta:0,duvida:0,tensao:0};return {...p,[ins.theme]:{...prev,[ins.sentiment]:(prev[ins.sentiment]||0)+1}};});
    setGroupsByTheme(p=>{const prev=p[ins.theme]||[];return prev.includes(ins.group)?p:{...p,[ins.theme]:[...prev,ins.group]};});
    setTrendByTheme(p=>({...p,[ins.theme]:Math.random()>0.5?"up":"stable"}));
    if (ins.question) setQuestionByTheme(p=>({...p,[ins.theme]:ins.question}));
    setConv(p=>{
      const next = {...p};
      CASES.forEach(c => {
        const prev = next[c.id] || 15;
        next[c.id] = Math.max(5,Math.min(95, prev + (Math.random()>0.4?Math.random()*7:-Math.random()*3)));
      });
      return next;
    });
    // Also produce mission output in demo mode
    if (missionIx.current < MISSION_POOL.length) {
      const mRaw = MISSION_POOL[missionIx.current];
      missionIx.current++;
      setMissionOutputs(p => [...p, {...mRaw, ts: Date.now()}]);
    }
  }, [detectBirthAndMilestones]);

  useEffect(() => {
    if (dataSource === "demo") {
      const iv = setInterval(tick, 2800);
      return () => clearInterval(iv);
    } else {
      // LIVE MODE: poll Supabase every 3s
      if (!SESSION_ID) return; // wait until session is resolved
      let mounted = true;
      async function poll() {
        try {
          const [rawInsights, sessions, participants, rawMission, rawQaQuestions] = await Promise.all([
            supaFetch("insights", `session_id=eq.${SESSION_ID}&order=created_at.asc`),
            supaFetch("sessions", `id=eq.${SESSION_ID}&select=is_live`),
            supaFetch("participants", `session_id=eq.${SESSION_ID}&select=id,display_name,group_id`),
            supaFetch("mission_outputs", `session_id=eq.${SESSION_ID}&order=created_at.asc`),
            supaFetch("qa_questions", `session_id=eq.${SESSION_ID}&order=question_order.asc`),
          ]);
          if (!mounted) return;
          console.log("[LIVE] Poll:", rawInsights?.length, "insights,", participants?.length, "participants");
          if (Array.isArray(rawInsights) && rawInsights.length > 0) {
            const processed = processLiveInsights(rawInsights);
            // Detect births and milestones
            Object.entries(processed.counts).forEach(([id, count]) => {
              detectBirthAndMilestones(id, count);
            });
            prevCountsRef.current = {...processed.counts};
            setCounts(processed.counts);
            setSentsMap(processed.sentsMap);
            setLastQuote(processed.lastQuote);
            setGThemes(processed.gThemes);
            setGEdges(processed.gEdges);
            setST(processed.sT);
            setSentsByTheme(processed.sentsByTheme);
            setGroupsByTheme(processed.groupsByTheme);
            setTrendByTheme(processed.trendByTheme);
            setQuestionByTheme(processed.questionByTheme);
            setInsights(processed.insights);
            setTotal(rawInsights.length);
            setConv(calcConvergence(processed.gEdges, CASE_GROUP_MAP));
            // Flash hit on newest insight
            if (rawInsights.length > prevInsightCount.current && prevInsightCount.current > 0) {
              const newest = rawInsights[rawInsights.length - 1];
              setHits(p => ({...p, [newest.theme]: true, ["g"+newest.group_id]: true}));
              setTimeout(() => setHits(p => {const n={...p}; delete n[newest.theme]; delete n["g"+newest.group_id]; return n;}), 3000);
            }
            prevInsightCount.current = rawInsights.length;
          }
          if (Array.isArray(rawMission) && rawMission.length > 0) {
            setMissionOutputs(processMissionOutputs(rawMission));
          }
          if (Array.isArray(rawQaQuestions)) {
            setQaQuestions(rawQaQuestions);
          }
          // sessions data used for is_live check if needed
          if (Array.isArray(participants)) {
            setActive(participants.length);
            setParticipantsList(participants);
          }
        } catch (err) { console.error("Poll error:", err); }
      }
      poll();
      const iv = setInterval(poll, 3000);
      return () => { mounted = false; clearInterval(iv); };
    }
  }, [dataSource, tick, currentSessionId]);

  const sorted = useMemo(()=>
    Object.entries(counts).filter(([id])=>id!=="qa_response").map(([id,count])=>({...getTheme(id),count})).sort((a,b)=>b.count-a.count), [counts]);
  // Treemap rects will be computed at render time using container dimensions
  const [mosaicSize, setMosaicSize] = useState({ w: 0, h: 0 });
  const mosaicRef = useRef(null);
  const mosaicObsRef = useRef(null);
  useEffect(() => {
    const node = mosaicRef.current;
    if (!node) { setMosaicSize({ w: 0, h: 0 }); return; }
    const measure = () => {
      const r = node.getBoundingClientRect();
      if (r.width > 0 && r.height > 0) {
        setMosaicSize({ w: r.width, h: r.height });
      }
    };
    // Measure after browser paint
    requestAnimationFrame(measure);
    // Also observe resize
    if (mosaicObsRef.current) mosaicObsRef.current.disconnect();
    const obs = new ResizeObserver(entries => {
      const { width, height } = entries[0].contentRect;
      if (width > 0 && height > 0) setMosaicSize({ w: width, h: height });
    });
    obs.observe(node);
    mosaicObsRef.current = obs;
    return () => obs.disconnect();
  }, [sorted.length > 0, mode]);
  const treemapRects = useMemo(() => {
    if (!mosaicSize.w || !mosaicSize.h || sorted.length === 0) return [];
    return computeTreemap(sorted, { x: 0, y: 0, w: mosaicSize.w, h: mosaicSize.h });
  }, [sorted, mosaicSize]);

  const isGrafos = mode==="grafos";
  const totalS = Object.values(sT).reduce((a,b)=>a+b,0);

  return (
    <div style={{
      width:"100%",height:"100vh",
      background:"linear-gradient(160deg, #0a0a0f 0%, #0d0c14 30%, #0a0f0d 60%, #08080c 100%)",
      fontFamily:"'DM Sans',sans-serif",color:"#fff",
      display:"flex",flexDirection:"column",overflow:"hidden",
    }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,500;9..40,700;9..40,800&family=DM+Mono:wght@400;500&display=swap');
        @keyframes panelIn{from{opacity:0;transform:translateX(60px)}to{opacity:1;transform:translateX(0)}}
        @keyframes slideIn{from{opacity:0;transform:translateX(20px)}to{opacity:1;transform:translateX(0)}}
        @keyframes tickerSlide{from{opacity:0;transform:translateX(80px)}to{opacity:1;transform:translateX(0)}}
        @keyframes live{0%,100%{opacity:1}50%{opacity:0.2}}
        @keyframes breathe{0%,100%{opacity:0.4;transform:scale(1)}50%{opacity:0.7;transform:scale(1.02)}}
        @keyframes menuIn{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}
        @keyframes menuBgIn{from{opacity:0}to{opacity:1}}
        @keyframes genesis{0%{transform:scale(0);opacity:0;filter:blur(20px) brightness(3)}30%{transform:scale(1.15);opacity:1;filter:blur(4px) brightness(2)}60%{transform:scale(0.95);filter:blur(0) brightness(1.3)}100%{transform:scale(1);opacity:1;filter:blur(0) brightness(1)}}
        @keyframes genesisRing{0%{transform:scale(0);opacity:0.8}100%{transform:scale(4);opacity:0}}
        @keyframes genesisFlash{0%{opacity:0}15%{opacity:1}100%{opacity:0}}
        @keyframes crystallize{0%{transform:scale(0.85);opacity:0;filter:blur(8px)}60%{transform:scale(1.03);opacity:0.9;filter:blur(1px)}100%{transform:scale(1);opacity:1;filter:blur(0)}}
        @keyframes tileHit{0%{transform:scale(1)}20%{transform:scale(1.04)}50%{transform:scale(0.99)}100%{transform:scale(1)}}
        @keyframes milestoneIn{0%{transform:scale(0) translateY(4px);opacity:0}40%{transform:scale(1.2) translateY(-2px);opacity:1}100%{transform:scale(1) translateY(0);opacity:1}}
        @keyframes milestoneFade{0%,70%{opacity:1}100%{opacity:0}}
        ::-webkit-scrollbar{width:3px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:#1a1a1a;border-radius:2px}
        *{box-sizing:border-box;margin:0;padding:0}
      `}</style>

      {/* HEADER — minimalist */}
      <header style={{
        background:"rgba(10,10,15,0.6)",
        backdropFilter:"blur(16px) saturate(150%)",WebkitBackdropFilter:"blur(16px) saturate(150%)",
        position:"relative",zIndex:50,
        borderBottom:"1px solid rgba(255,255,255,0.06)",
      }}>
        {/* Main row */}
        <div style={{
          padding:"10px 28px",display:"flex",justifyContent:"space-between",alignItems:"center",
        }}>
          {/* LEFT — S&CO Logo */}
          <div style={{ display:"flex",alignItems:"center",gap:0 }}>
            <div style={{ lineHeight:0.85,fontWeight:900,fontFamily:"'DM Sans',sans-serif",color:"#fff",letterSpacing:-1 }}>
              <div style={{ fontSize:22,letterSpacing:1 }}>SERVICE</div>
              <div style={{ fontSize:22,letterSpacing:1 }}>&CO</div>
            </div>
          </div>

          {/* CENTER — Session title */}
          <div style={{ position:"absolute",left:"50%",transform:"translateX(-50%)",textAlign:"center" }}>
            <div style={{ fontSize:14,fontWeight:600,color:"#fff",letterSpacing:0.5 }}>
              {allSessions.find(s=>s.id===currentSessionId)?.name || "Sessions"}
            </div>
            <div style={{ display:"flex",alignItems:"center",justifyContent:"center",gap:8,marginTop:3 }}>
              <div style={{width:6,height:6,borderRadius:"50%",background:dataSource==="live"?"#3DFFA2":"#FFD93D",animation:"live 1.5s ease infinite"}} />
              <span style={{fontSize:9,color:dataSource==="live"?"#3DFFA2":"#FFD93D",fontWeight:700,letterSpacing:3,textTransform:"uppercase",fontFamily:"'DM Mono',monospace"}}>{dataSource==="live"?"LIVE":"DEMO"}</span>
            </div>
          </div>

          {/* RIGHT — Clock + hamburger */}
          <div style={{ display:"flex",alignItems:"center",gap:20 }}>
            <div style={{ fontSize:32,fontWeight:300,color:"#fff",fontFamily:"'DM Mono',monospace",letterSpacing:4,lineHeight:1 }}>
              {clockTime}
            </div>
            <div onClick={() => setMenuOpen(true)} style={{
              cursor:"pointer",display:"flex",flexDirection:"column",gap:5,padding:6,
            }}>
              <div style={{width:24,height:2,background:"#fff",borderRadius:1}} />
              <div style={{width:18,height:2,background:"rgba(255,255,255,0.6)",borderRadius:1}} />
              <div style={{width:24,height:2,background:"#fff",borderRadius:1}} />
            </div>
          </div>
        </div>

        {/* Feed row — insight ticker */}
        {insights.length > 0 && (() => {
          const ins = insights[Math.min(tickerIdx, insights.length - 1)];
          const th = getTheme(ins.theme);
          const se = SENTS[ins.sentiment];
          const g = GROUPS.find(gr => gr.id === ins.group);
          return (
            <div style={{
              padding:"6px 28px",
              borderTop:"1px solid rgba(255,255,255,0.04)",
              display:"flex",alignItems:"center",gap:14,
              overflow:"hidden",
            }}>
              <div key={ins.ts || tickerIdx} style={{
                display:"flex",alignItems:"center",gap:12,flex:1,minWidth:0,
                animation:"tickerSlide 0.4s cubic-bezier(0.16,1,0.3,1)",
              }}>
                <span style={{fontSize:14,color:se?.color,fontWeight:800,flexShrink:0}}>{se?.icon}</span>
                <span style={{
                  fontSize:11,fontWeight:700,color:th.color,textTransform:"uppercase",
                  letterSpacing:2,flexShrink:0,fontFamily:"'DM Mono',monospace",
                }}>{th.label}</span>
                <div style={{width:1,height:14,background:"rgba(255,255,255,0.1)",flexShrink:0}} />
                <span style={{
                  fontSize:12,color:"rgba(255,255,255,0.55)",lineHeight:1.3,
                  whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",flex:1,
                }}>{ins.text}</span>
              </div>
              <div style={{display:"flex",alignItems:"center",gap:6,flexShrink:0}}>
                <div style={{width:7,height:7,borderRadius:"50%",background:g?.caseData?.color||"#888"}} />
                <span style={{fontSize:11,color:"#666",fontFamily:"'DM Mono',monospace",letterSpacing:1}}>{g?.label || ("G"+ins.group)}</span>
              </div>
            </div>
          );
        })()}
      </header>

      {/* NAVIGATION OVERLAY */}
      {menuOpen && (
        <div onClick={() => setMenuOpen(false)} style={{
          position:"fixed",inset:0,zIndex:500,
          background:"rgba(5,5,10,0.92)",
          backdropFilter:"blur(24px)",WebkitBackdropFilter:"blur(24px)",
          animation:"menuBgIn 0.25s ease forwards",
          display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",
        }}>
          <div onClick={e => e.stopPropagation()} style={{
            animation:"menuIn 0.3s ease forwards",
            display:"flex",flexDirection:"column",alignItems:"center",gap:36,
            maxWidth:420,width:"90%",
          }}>
            {/* Close button */}
            <div onClick={() => setMenuOpen(false)} style={{
              position:"fixed",top:20,right:28,cursor:"pointer",
              fontSize:28,color:"#fff",fontWeight:300,lineHeight:1,
            }}>✕</div>

            {/* VIEW MODE */}
            <div style={{ display:"flex",flexDirection:"column",gap:0,width:"100%" }}>
              <div style={{fontSize:9,color:"#555",textTransform:"uppercase",letterSpacing:3,fontWeight:700,marginBottom:12,fontFamily:"'DM Mono',monospace"}}>Visualização</div>
              {["mosaico","grafos","missao","pr"].map(m => {
                const mColors = { mosaico:"#3DFFA2", grafos:"#B388FF", missao:"#448AFF", pr:"#FF6B6B" };
                const mLabels = { mosaico:"MOSAICO", grafos:"GRAFOS", missao:"MISSÃO", pr:"P&R" };
                const mIcons = {
                  mosaico: React.createElement("svg", {width:22,height:22,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},
                    React.createElement("path", {d:"M4 6.1C4 5.54 4 5.26 4.109 5.046C4.205 4.858 4.358 4.705 4.546 4.609C4.76 4.5 5.04 4.5 5.6 4.5H8.4C8.96 4.5 9.24 4.5 9.454 4.609C9.642 4.705 9.795 4.858 9.891 5.046C10 5.26 10 5.54 10 6.1V8.9C10 9.46 10 9.74 9.891 9.954C9.795 10.142 9.642 10.295 9.454 10.391C9.24 10.5 8.96 10.5 8.4 10.5H5.6C5.04 10.5 4.76 10.5 4.546 10.391C4.358 10.295 4.205 10.142 4.109 9.954C4 9.74 4 9.46 4 8.9V6.1Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                    React.createElement("path", {d:"M13 6C13 5.534 13 5.301 13.076 5.117C13.178 4.872 13.372 4.678 13.617 4.576C13.801 4.5 14.034 4.5 14.5 4.5H18.5C18.966 4.5 19.199 4.5 19.383 4.576C19.628 4.678 19.822 4.872 19.924 5.117C20 5.301 20 5.534 20 6C20 6.466 20 6.699 19.924 6.883C19.822 7.128 19.628 7.322 19.383 7.424C19.199 7.5 18.966 7.5 18.5 7.5H14.5C14.034 7.5 13.801 7.5 13.617 7.424C13.372 7.322 13.178 7.128 13.076 6.883C13 6.699 13 6.466 13 6Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                    React.createElement("path", {d:"M13 12C13 11.534 13 11.301 13.076 11.117C13.178 10.872 13.372 10.678 13.617 10.576C13.801 10.5 14.034 10.5 14.5 10.5H18.5C18.966 10.5 19.199 10.5 19.383 10.576C19.628 10.678 19.822 10.872 19.924 11.117C20 11.301 20 11.534 20 12C20 12.466 20 12.699 19.924 12.883C19.822 13.128 19.628 13.322 19.383 13.424C19.199 13.5 18.966 13.5 18.5 13.5H14.5C14.034 13.5 13.801 13.5 13.617 13.424C13.372 13.322 13.178 13.128 13.076 12.883C13 12.699 13 12.466 13 12Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                    React.createElement("path", {d:"M13 18C13 17.534 13 17.301 13.076 17.117C13.178 16.872 13.372 16.678 13.617 16.576C13.801 16.5 14.034 16.5 14.5 16.5H18.5C18.966 16.5 19.199 16.5 19.383 16.576C19.628 16.678 19.822 16.872 19.924 17.117C20 17.301 20 17.534 20 18C20 18.466 20 18.699 19.924 18.883C19.822 19.128 19.628 19.322 19.383 19.424C19.199 19.5 18.966 19.5 18.5 19.5H14.5C14.034 19.5 13.801 19.5 13.617 19.424C13.372 19.322 13.178 19.128 13.076 18.883C13 18.699 13 18.466 13 18Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                    React.createElement("path", {d:"M4 15.1C4 14.54 4 14.26 4.109 14.046C4.205 13.858 4.358 13.705 4.546 13.609C4.76 13.5 5.04 13.5 5.6 13.5H8.4C8.96 13.5 9.24 13.5 9.454 13.609C9.642 13.705 9.795 13.858 9.891 14.046C10 14.26 10 14.54 10 15.1V17.9C10 18.46 10 18.74 9.891 18.954C9.795 19.142 9.642 19.295 9.454 19.391C9.24 19.5 8.96 19.5 8.4 19.5H5.6C5.04 19.5 4.76 19.5 4.546 19.391C4.358 19.295 4.205 19.142 4.109 18.954C4 18.74 4 18.46 4 17.9V15.1Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                  ),
                  grafos: React.createElement("svg", {width:22,height:22,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},
                    React.createElement("path", {d:"M9 13C9 15.209 10.791 17 13 17C14.105 17 15.105 16.552 15.828 15.828M9 13H5M9 13C9 11.448 9.884 10.103 11.176 9.439M5 13C5 13.552 4.552 14 4 14C3.448 14 3 13.552 3 13C3 12.448 3.448 12 4 12C4.552 12 5 12.448 5 13ZM19.293 6.707C19.112 6.526 19 6.276 19 6C19 5.448 19.448 5 20 5C20.552 5 21 5.448 21 6C21 6.552 20.552 7 20 7C19.724 7 19.474 6.888 19.293 6.707ZM19.293 6.707L15.828 10.172M9.371 4.929C9.74 4.781 10 4.421 10 4C10 3.448 9.552 3 9 3C8.448 3 8 3.448 8 4C8 4.552 8.448 5 9 5C9.131 5 9.257 4.975 9.371 4.929ZM9.371 4.929L11.176 9.439M15.828 15.828C16.552 15.105 17 14.105 17 13C17 11.895 16.552 10.895 15.828 10.172M15.828 15.828L19.293 19.293M19.293 19.293C19.112 19.474 19 19.724 19 20C19 20.552 19.448 21 20 21C20.552 21 21 20.552 21 20C21 19.448 20.552 19 20 19C19.724 19 19.474 19.112 19.293 19.293ZM15.828 10.172C15.105 9.448 14.105 9 13 9C12.343 9 11.723 9.158 11.176 9.439",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                  ),
                  missao: React.createElement("svg", {width:22,height:22,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},
                    React.createElement("path", {d:"M21 12C21 16.971 16.971 21 12 21C7.029 21 3 16.971 3 12C3 7.029 7.029 3 12 3M17 12C17 14.761 14.761 17 12 17C9.239 17 7 14.761 7 12C7 9.239 9.239 7 12 7M14.758 9.352L18.717 9.767L20.863 6.763L18.288 5.905L17.429 3.329L14.425 5.475L14.758 9.352ZM14.758 9.352L12 12",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),
                  ),
                  pr: React.createElement("svg", {width:22,height:22,viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg"},
                    React.createElement("path", {d:"M11.967 12.75C12.967 11.75 13.967 11.355 13.967 10.25C13.967 9.145 13.072 8.25 11.967 8.25C11.035 8.25 10.252 8.887 10.03 9.75M11.967 15.75H11.977M21 12C21 16.971 16.971 21 12 21C7.029 21 3 16.971 3 12C3 7.029 7.029 3 12 3C16.971 3 21 7.029 21 12Z",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round"}),
                  ),
                };
                const mc = mColors[m];
                const active = mode === m;
                return (
                  <div key={m} onClick={() => { setMode(m); setMenuOpen(false); }} style={{
                    padding:"14px 20px",cursor:"pointer",
                    borderLeft:active ? ("3px solid " + mc) : "3px solid transparent",
                    background:active ? mc+"10" : "transparent",
                    display:"flex",alignItems:"center",gap:14,
                    color:active ? mc : "#666",
                    transition:"all 0.2s ease",
                  }}>
                    {mIcons[m]}
                    <span style={{fontSize:18,fontWeight:active?700:400,letterSpacing:3}}>{mLabels[m]}</span>
                  </div>
                );
              })}
            </div>

            {/* DATA SOURCE */}
            <div style={{ width:"100%" }}>
              <div style={{fontSize:9,color:"#555",textTransform:"uppercase",letterSpacing:3,fontWeight:700,marginBottom:12,fontFamily:"'DM Mono',monospace"}}>Fonte de dados</div>
              <div style={{ display:"flex",gap:0,borderRadius:8,overflow:"hidden",border:"1px solid rgba(255,255,255,0.1)" }}>
                {["demo","live"].map(d=>(
                  <div key={d} onClick={()=>{
                    setDataSource(d);
                    if (d==="live") {
                      setInsights([]); setCounts({});
                      setSentsMap({}); setLastQuote({}); setHits({}); setGThemes({});
                      setGEdges({}); setConv({}); setTotal(0); setActive(0);
                      setST({proposta:0,duvida:0,tensao:0}); prevInsightCount.current=0;
                      isGenesisComplete.current=false; setGenesisActive(false);
                      setNewThemes({}); setMilestones({}); prevCountsRef.current={};
                      setMissionOutputs([]); setQaQuestions([]); missionIx.current=0;
                      loadActivityGroups().then(() => setGroupsVersion(v => v + 1));
                    }
                  }} style={{
                    flex:1,padding:"12px 0",cursor:"pointer",textAlign:"center",
                    background:dataSource===d?(d==="live"?"#3DFFA218":"#FFD93D18"):"transparent",
                    fontSize:14,fontWeight:700,textTransform:"uppercase",letterSpacing:2,
                    color:dataSource===d?(d==="live"?"#3DFFA2":"#FFD93D"):"#555",
                    transition:"all 0.3s ease",
                  }}>{d}</div>
                ))}
              </div>
            </div>

            {/* STATS */}
            <div style={{ display:"flex",gap:24,width:"100%",justifyContent:"center" }}>
              {[{v:total,l:"Interações",c:"#3DFFA2"},{v:Math.min(active,54),l:"Ativos",c:"#448AFF"},{v:insights.length,l:"Insights",c:"#B388FF"}].map(s=>(
                <div key={s.l} style={{textAlign:"center"}}>
                  <div style={{fontSize:32,fontWeight:800,color:s.c,fontFamily:"'DM Mono',monospace",lineHeight:1,letterSpacing:-1}}>{s.v}</div>
                  <div style={{fontSize:9,color:"#888",textTransform:"uppercase",letterSpacing:2,fontWeight:700,marginTop:6}}>{s.l}</div>
                </div>
              ))}
            </div>

            {/* SENTIMENT */}
            <div style={{ width:"100%",display:"flex",flexDirection:"column",alignItems:"center",gap:8 }}>
              <div style={{display:"flex",height:4,width:"100%",gap:2,borderRadius:2,overflow:"hidden"}}>
                {Object.entries(SENTS).map(([k,v])=>{
                  const pct=totalS?((sT[k]||0)/totalS)*100:33;
                  return <div key={k} style={{width:pct+"%",background:v.color,transition:"width 0.8s ease"}} />;
                })}
              </div>
              <div style={{display:"flex",gap:16}}>
                {Object.entries(SENTS).map(([k,v])=>(
                  <div key={k} style={{display:"flex",alignItems:"center",gap:5}}>
                    <div style={{width:6,height:6,borderRadius:1,background:v.color}} />
                    <span style={{fontSize:12,color:"#999",fontFamily:"'DM Mono',monospace"}}>{v.label} {sT[k]}</span>
                  </div>
                ))}
              </div>
            </div>

            {/* SESSION PICKER + QR */}
            <div style={{ display:"flex",gap:12,width:"100%",justifyContent:"center" }}>
              {allSessions.length > 1 && (
                <select value={currentSessionId || ""} onChange={e => { switchSession(e.target.value); }} style={{
                  background:"rgba(255,255,255,0.05)", border:"1px solid rgba(255,255,255,0.1)",
                  borderRadius:8, padding:"10px 16px", color:"#aaa", fontSize:13, fontWeight:700,
                  fontFamily:"'DM Sans',sans-serif", letterSpacing:1, textTransform:"uppercase",
                  outline:"none", cursor:"pointer", flex:1,
                }}>
                  {allSessions.map(s => (
                    <option key={s.id} value={s.id} style={{background:"#111",color:"#fff"}}>
                      {s.name.substring(0,25)}{s.is_live?" ●":""}
                    </option>
                  ))}
                </select>
              )}
              <div onClick={() => { setShowQR(q => !q); setMenuOpen(false); }} style={{
                padding:"10px 20px", cursor:"pointer", borderRadius:8,
                border:"1px solid rgba(255,255,255,0.15)",
                background: showQR ? "rgba(61,255,162,0.15)" : "rgba(255,255,255,0.05)",
                fontSize:14, fontWeight:700, textTransform:"uppercase", letterSpacing:2,
                color: showQR ? "#3DFFA2" : "#aaa",
              }}>QR Code</div>
            </div>
          </div>
        </div>
      )}

      {/* BODY */}
      {mode === "pr" ? (
        <PerguntaRespostaView
          questions={qaQuestions}
          insights={insights}
          groups={GROUPS}
          cases={CASES}
          participants={participantsList}
        />
      ) : mode === "missao" ? (
        <MissaoView
          outputs={missionOutputs}
          filterGroup={filterGroup}
          groups={GROUPS}
          cases={CASES}
        />
      ) : isGrafos ? (
        <div style={{ flex:1,position:"relative",overflow:"hidden" }}>
          <GrafosView groupThemeEdges={gEdges} themeCounts={counts} convergenceData={conv} onNodeClick={setSelection} groups={GROUPS} groupsVersion={groupsVersion} />
        </div>
      ) : sorted.length === 0 ? (
        <div style={{ flex:1,position:"relative",overflow:"hidden" }}>
          <WaitingRoom activeCount={active} />
        </div>
      ) : (
        <div ref={mosaicRef} style={{ flex:1,position:"relative",overflow:"hidden",margin:"6px 10px 10px" }}>
          {treemapRects.map((t) => {
            const totalArea = mosaicSize.w * mosaicSize.h;
            return <Tile key={t.id} theme={t} count={t.count} sentiment={sentsMap[t.id]} lastInsight={lastQuote[t.id]} hit={!!hits[t.id]} rect={t.rect} totalArea={totalArea} onClick={()=>setSelection({type:"theme",id:t.id})} isNew={!!newThemes[t.id]} isGenesis={!!newThemes[t.id] && genesisActive} milestone={milestones[t.id]} sentBreakdown={sentsByTheme[t.id]} themeGroups={groupsByTheme[t.id]} trend={trendByTheme[t.id]} question={questionByTheme[t.id]} />;
          })}
        </div>
      )}

      {/* DETAIL PANEL */}
      {selection && (
        <div onClick={()=>setSelection(null)} style={{position:"fixed",inset:0,zIndex:150,background:"rgba(0,0,0,0.3)"}} />
      )}
      <DetailPanel selection={selection} insights={insights} onClose={()=>setSelection(null)} questionByTheme={questionByTheme} />

      {/* QR CODE — top-right corner overlay */}
      {showQR && (
        <div style={{position:"fixed",top:60,right:20,zIndex:400,display:"flex",flexDirection:"column",alignItems:"center",gap:10,padding:20,borderRadius:12,background:"rgba(15,15,20,0.92)",border:"1px solid rgba(61,255,162,0.2)",boxShadow:"0 8px 32px rgba(0,0,0,0.5)"}}>
          <span style={{fontSize:10,fontWeight:700,letterSpacing:3,textTransform:"uppercase",color:"#3DFFA2"}}>Escaneie para participar</span>
          {qrDataUrl ? (
            <img src={qrDataUrl} alt="QR Code" style={{width:180,height:180,borderRadius:6}} />
          ) : (
            <div style={{width:180,height:180,display:"flex",alignItems:"center",justifyContent:"center",background:"#222",borderRadius:6,color:"#666",fontSize:12}}>Gerando QR...</div>
          )}
          <span style={{fontSize:10,color:"#666",fontFamily:"'DM Mono',monospace",maxWidth:200,textAlign:"center",wordBreak:"break-all"}}>
            {`sessions-inky-five.vercel.app/?session=${currentSessionId || SESSION_ID}`}
          </span>
        </div>
      )}

      {/* GENESIS OVERLAY */}
      {genesisActive && (
        <div style={{position:"fixed",inset:0,zIndex:300,pointerEvents:"none",display:"flex",alignItems:"center",justifyContent:"center"}}>
          <div style={{width:100,height:100,borderRadius:"50%",border:"2px solid #3DFFA2",animation:"genesisRing 1.5s ease-out forwards"}} />
          <div style={{position:"absolute",width:"100%",height:"100%",background:"radial-gradient(circle, rgba(61,255,162,0.15) 0%, transparent 60%)",animation:"genesisFlash 1.2s ease-out forwards"}} />
        </div>
      )}

      {/* AMBIENT CIRCLES */}
      <div style={{position:"fixed",top:60,left:-80,width:240,height:240,borderRadius:"50%",border:"1px solid rgba(61,255,162,0.04)",pointerEvents:"none",zIndex:0}} />
      <div style={{position:"fixed",bottom:-60,right:180,width:340,height:340,borderRadius:"50%",border:"1px solid rgba(179,136,255,0.03)",pointerEvents:"none",zIndex:0}} />
    </div>
  );
}
