const $ = (id) => document.getElementById(id);

let settings;
let rendered = false;
const _TAB_TITLES = { home: 'Início', project: 'Projeto', chat: 'Chat' };

// Detecta se está rodando dentro do painel flutuante (iframe)
const IN_PANEL = window.parent !== window;
if (IN_PANEL) document.documentElement.classList.add('in-panel');

// Detecta modo janela (?mode=window) — esconde o botão de "abrir em janela"
const IN_WINDOW_MODE = new URLSearchParams(location.search).get('mode') === 'window';
if (IN_WINDOW_MODE) document.documentElement.classList.add('in-window');

/* ============================================================
   Busca a aba do Lovable correta — funciona em sidepanel,
   popup, ou modo janela flutuante.

   - No sidepanel/popup: a "currentWindow" é a do navegador → active OK
   - No modo janela: a "currentWindow" é a JANELA FLUTUANTE → procura
     em outras janelas pela aba ativa do Lovable.
   ============================================================ */
async function getLovableTab() {
  // 1) Tenta primeiro a aba ativa da janela atual (sidepanel/popup case)
  if (!IN_WINDOW_MODE) {
    try {
      const [t] = await chrome.tabs.query({ active: true, currentWindow: true });
      if (t?.id && /https:\/\/lovable\.dev\//i.test(t.url || '')) return t;
    } catch (_) {}
  }
  // 2) Busca em TODAS as janelas (normal, sem incluir popups flutuantes)
  try {
    const allWindows = await chrome.windows.getAll({ populate: true, windowTypes: ['normal'] });
    let projectActive = null;
    let projectAny = null;
    let lovableActive = null;
    let lovableAny = null;
    for (const w of allWindows) {
      for (const t of (w.tabs || [])) {
        const u = t.url || '';
        if (!/https:\/\/lovable\.dev\//i.test(u)) continue;
        const isProject = /https:\/\/lovable\.dev\/projects\//i.test(u);
        if (isProject && t.active) { projectActive = t; break; }
        if (isProject && !projectAny) projectAny = t;
        if (!lovableActive && t.active) lovableActive = t;
        if (!lovableAny) lovableAny = t;
      }
      if (projectActive) break;
    }
    return projectActive || projectAny || lovableActive || lovableAny || null;
  } catch (_) {}
  // 3) Último fallback
  try {
    const all = await chrome.tabs.query({ url: 'https://lovable.dev/*' });
    return all?.find((t) => /\/projects\//i.test(t.url || '')) || all?.[0] || null;
  } catch (_) {}
  return null;
}

init();

async function init() {
  try { injectSVGDefs(); } catch (_) {}
  try { if (!IN_PANEL) spawnParticles(); } catch (_) {}
  try { showSkeletons(); } catch (_) {}
  try { setVersion(); } catch (_) {}
  try { bindEvents(); } catch (e) { console.error('[LOV] bindEvents error:', e); }

  try {
    settings = await sendMessage({ type: 'GET_SETTINGS' });
  } catch (e) {
    console.error('[LOV] GET_SETTINGS error:', e);
    settings = null;
  }

  try { applyTheme(settings?.theme || 'dark'); } catch (_) {}
  try { hideSkeletons(); } catch (_) {}

  try {
    render();
    rendered = true;
  } catch (e) {
    console.error('[LOV] render error:', e);
    // Fallback: garante que a tela de licença seja visível
    try {
      document.getElementById('view-license').hidden = false;
      document.getElementById('view-main').hidden = true;
    } catch (_) {}
  }

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === 'local' && changes.settings) {
      settings = changes.settings.newValue;
      try { applyTheme(settings?.theme || 'dark'); } catch (_) {}
      try { render(); } catch (_) {}
    }
  });
}

/* ============================================================
   Tema (light / dark)
   ============================================================ */
function applyTheme(theme) {
  const t = theme === 'light' ? 'light' : 'dark';
  document.documentElement.dataset.theme = t;
}

async function toggleTheme() {
  const current = settings?.theme === 'light' ? 'light' : 'dark';
  const next = current === 'light' ? 'dark' : 'light';
  applyTheme(next);
  const updated = await sendMessage({ type: 'SET_SETTINGS', updates: { theme: next } });
  if (updated) settings = updated;
}

/* ============================================================
   Abrir extensão em janela flutuante (modo janela)
   ============================================================ */
async function openInWindow() {
  try {
    const url = chrome.runtime.getURL('popup/popup.html?mode=window');
    await chrome.windows.create({
      url,
      type: 'popup',
      width: 420,
      height: 720,
      focused: true,
    });
  } catch (e) {
    console.error('[PULSE popup] open in window failed:', e);
  }
}

/* ============================================================
   SVG Gradient for power ring
   ============================================================ */
function injectSVGDefs() {
  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
  svg.setAttribute('width', '0');
  svg.setAttribute('height', '0');
  svg.style.position = 'absolute';
  svg.innerHTML = `
    <defs>
      <linearGradient id="pulse-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
        <stop offset="0%" stop-color="#FF5A00"/>
        <stop offset="30%" stop-color="#FF2D55"/>
        <stop offset="60%" stop-color="#A259FF"/>
        <stop offset="100%" stop-color="#5B8CFF"/>
      </linearGradient>
    </defs>
  `;
  document.body.prepend(svg);
}

/* ============================================================
   12. Micro Particles (detalhe fino)
   ============================================================ */
function spawnParticles() {
  const container = $('particles');
  if (!container) return;
  for (let i = 0; i < 12; i++) {
    const p = document.createElement('div');
    p.className = 'particle';
    p.style.left = Math.random() * 100 + '%';
    p.style.bottom = -(Math.random() * 20) + '%';
    p.style.width = (1 + Math.random() * 2) + 'px';
    p.style.height = p.style.width;
    p.style.animationDuration = (8 + Math.random() * 16) + 's';
    p.style.animationDelay = -(Math.random() * 20) + 's';
    p.style.opacity = 0.1 + Math.random() * 0.2;
    const colors = ['rgba(162,89,255,0.4)', 'rgba(255,90,0,0.3)', 'rgba(91,140,255,0.3)'];
    p.style.background = colors[Math.floor(Math.random() * colors.length)];
    container.appendChild(p);
  }
}

/* ============================================================
   4. Skeleton Loading (alto impacto)
   ============================================================ */
function showSkeletons() {
  // Add skeleton class to key elements
  document.querySelectorAll('.info-value, .usage-today, .power-label').forEach(el => {
    el.dataset.original = el.textContent;
    el.textContent = '     ';
    el.classList.add('skeleton');
  });
}

function hideSkeletons() {
  document.querySelectorAll('.skeleton').forEach(el => {
    el.classList.remove('skeleton');
    if (el.dataset.original) {
      el.textContent = el.dataset.original;
      delete el.dataset.original;
    }
  });
}

/* ============================================================
   Render
   ============================================================ */
function render() {
  const status = settings?.licenseState?.status;
  const showMain = status === 'valid';

  $('view-license').hidden = showMain;
  $('view-main').hidden = !showMain;

  const licChange = $('lic-change');
  if (licChange) licChange.hidden = !showMain;

  // Botões de topbar que só aparecem com licença válida
  const openOpts = $('open-options');
  const winMode  = $('window-mode');
  if (openOpts) openOpts.hidden = !showMain;
  if (winMode)  winMode.hidden  = !showMain;

  if (!showMain) renderLicenseView();
  else renderMainView();
}

function renderLicenseView() {
  const state = settings?.licenseState || { status: 'unknown' };
  const headline = $('lic-headline');
  const msg = $('lic-msg');
  const err = $('lic-error');
  const input = $('lic-input');

  switch (state.status) {
    case 'expired':
      headline.textContent = 'Licença expirada';
      msg.textContent = 'Sua chave expirou. Cole uma nova pra continuar.';
      break;
    case 'wrong_email':
      headline.textContent = 'Conta incorreta';
      msg.textContent = 'Esta chave já foi ativada em outro email do Lovable.';
      break;
    case 'invalid':
      headline.textContent = 'Licença inválida';
      msg.textContent = 'Não encontramos essa chave. Confira e tente de novo.';
      break;
    case 'rate_limited':
      headline.textContent = 'Aguarde um instante';
      msg.textContent = 'Muitas tentativas. Tente novamente em um minuto.';
      break;
    default:
      headline.textContent = 'Ative sua licença';
      msg.textContent = 'Cole sua chave abaixo para desbloquear o Box Lovable.';
  }

  if (state.error && state.status !== 'unknown') {
    err.hidden = false;
    err.textContent = state.error;
  } else {
    err.hidden = true;
  }

  if (settings?.licenseKey && !input.value) input.value = settings.licenseKey;
  input.focus();
}

function renderMainView() {
  const enabled = !!settings?.enabled;
  const btn = $('big-toggle');
  btn.setAttribute('aria-pressed', enabled ? 'true' : 'false');
  $('power-label').textContent = enabled ? 'ATIVADO' : 'DESLIGADO';

  // Banner "Extensão Ativada" — visível só quando enabled
  const banner = $('active-banner');
  if (banner) banner.hidden = !enabled;

  // Ambient glow
  const glow = $('ambient-glow');
  if (glow) {
    if (enabled) glow.classList.add('active');
    else glow.classList.remove('active');
  }

  const ls = settings?.licenseState || {};
  $('lic-plan').textContent = formatPlan(ls.plan);
  $('lic-expires').textContent = formatExpires(ls.expiresAt);
  $('user-email').textContent = settings?.userEmail || '';

  // Topbar: badge de dias restantes
  const daysCount = $('days-count');
  const daysBadge = $('days-badge');
  if (daysCount && ls.expiresAt) {
    const diffMs = new Date(ls.expiresAt).getTime() - Date.now();
    const days = diffMs > 0 ? Math.floor(diffMs / 86400000) : 0;
    daysCount.textContent = days.toLocaleString('pt-BR');
    if (daysBadge) daysBadge.hidden = false;
  } else if (!ls.expiresAt) {
    // Sem expiração (licença vitalícia)
    if (daysCount) daysCount.textContent = '∞';
    if (daysBadge) daysBadge.hidden = false;
  }

  startCountdown();
  renderExpiryBar();
  renderUsageChart();
  updateDownloadButton();
}

/* ============================================================
   Barra de vigência da licença
   ============================================================ */
const PLAN_DURATIONS_MS = {
  day: 24 * 60 * 60 * 1000,
  week: 7 * 24 * 60 * 60 * 1000,
  month: 30 * 24 * 60 * 60 * 1000,
  pro: 30 * 24 * 60 * 60 * 1000,
  enterprise: 365 * 24 * 60 * 60 * 1000,
  free: 24 * 60 * 60 * 1000,
};

function renderExpiryBar() {
  const wrap = $('expiry-bar-wrap');
  const fill = $('expiry-bar-fill');
  const pctEl = $('expiry-bar-pct');
  if (!wrap || !fill || !pctEl) return;

  const ls = settings?.licenseState || {};
  const expiresAt = ls.expiresAt;
  const plan = ls.plan;

  if (!expiresAt) {
    wrap.hidden = true;
    return;
  }
  const expiresMs = new Date(expiresAt).getTime();
  if (Number.isNaN(expiresMs)) {
    wrap.hidden = true;
    return;
  }

  const now = Date.now();

  // Expirou
  if (now >= expiresMs) {
    wrap.hidden = false;
    wrap.dataset.zone = 'expired';
    fill.style.width = '100%';
    pctEl.textContent = 'EXPIRADA';
    return;
  }

  // Estima duração total pelo plano (fallback 24h)
  const duration = PLAN_DURATIONS_MS[plan] || PLAN_DURATIONS_MS.day;
  const startMs = expiresMs - duration;
  const totalMs = expiresMs - startMs;
  const elapsedMs = Math.max(0, now - startMs);
  const pct = Math.max(0, Math.min(100, (elapsedMs / totalMs) * 100));

  wrap.hidden = false;
  fill.style.width = pct.toFixed(2) + '%';
  pctEl.textContent = pct.toFixed(0) + '% usado';

  if (pct >= 85) wrap.dataset.zone = 'danger';
  else if (pct >= 60) wrap.dataset.zone = 'warn';
  else wrap.dataset.zone = 'safe';
}

/* ============================================================
   8. Usage Chart with cascade animation + number roll-up
   ============================================================ */
function renderUsageChart() {
  const daily = settings?.stats?.daily || {};
  const chart = $('usage-chart');
  const labels = $('usage-labels');
  const todayEl = $('usage-today');

  const days = [];
  const dayNames = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
  const now = new Date();

  for (let i = 6; i >= 0; i--) {
    const d = new Date(now);
    d.setDate(d.getDate() - i);
    const key = d.toISOString().slice(0, 10);
    const count = daily[key] || 0;
    days.push({
      key, count,
      label: i === 0 ? 'Hoje' : i === 1 ? 'Ontem' : dayNames[d.getDay()],
      isToday: i === 0,
    });
  }

  const maxCount = Math.max(...days.map(d => d.count), 1);
  const todayCount = days[days.length - 1].count;

  // 2. Number roll-up for today
  animateNumber(todayEl, todayCount, ' hoje');

  chart.innerHTML = '';
  labels.innerHTML = '';

  days.forEach((day, idx) => {
    const pct = Math.max((day.count / maxCount) * 100, day.count > 0 ? 8 : 3);

    const barWrap = document.createElement('div');
    barWrap.className = 'usage-bar-wrap';

    if (day.count > 0) {
      const num = document.createElement('span');
      num.className = 'usage-bar-num';
      num.textContent = day.count;
      // Cascade delay for numbers
      num.style.animationDelay = (idx * 0.06) + 's';
      barWrap.appendChild(num);
    }

    const bar = document.createElement('div');
    bar.className = 'usage-bar' + (day.isToday ? ' usage-bar-today' : '');
    // Cascade delay for bars
    bar.style.animationDelay = (idx * 0.06) + 's';
    bar.style.animationFillMode = 'forwards';
    bar.style.setProperty('--bar-height', pct + '%');
    bar.style.animation = `bar-grow-to 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) ${idx * 0.06}s forwards`;

    barWrap.appendChild(bar);
    chart.appendChild(barWrap);

    const lbl = document.createElement('span');
    lbl.className = 'usage-day-label' + (day.isToday ? ' usage-day-today' : '');
    lbl.textContent = day.label;
    labels.appendChild(lbl);
  });

  // Inject dynamic keyframes for bar heights
  let styleEl = document.getElementById('bar-animations');
  if (!styleEl) {
    styleEl = document.createElement('style');
    styleEl.id = 'bar-animations';
    document.head.appendChild(styleEl);
  }
  styleEl.textContent = `
    @keyframes bar-grow-to {
      from { height: 2px; }
      to { height: var(--bar-height); }
    }
  `;
}

/* ============================================================
   2. Number Roll-up Animation (alto impacto)
   ============================================================ */
function animateNumber(el, target, suffix) {
  suffix = suffix || '';
  if (!el) return;
  const current = parseInt(el.textContent) || 0;
  if (current === target) {
    el.textContent = target + suffix;
    return;
  }
  const duration = 400;
  const start = performance.now();
  const from = 0;

  function tick(now) {
    const elapsed = now - start;
    const progress = Math.min(elapsed / duration, 1);
    // Ease out cubic
    const eased = 1 - Math.pow(1 - progress, 3);
    const value = Math.round(from + (target - from) * eased);
    el.textContent = value + suffix;
    if (progress < 1) requestAnimationFrame(tick);
  }

  requestAnimationFrame(tick);
}

/* ============================================================
   3. Ripple Effect on Toggle (alto impacto)
   ============================================================ */
function spawnRipple() {
  const container = $('ripple-container');
  if (!container) return;
  const ripple = document.createElement('div');
  ripple.className = 'ripple';
  const size = 120;
  ripple.style.width = size + 'px';
  ripple.style.height = size + 'px';
  ripple.style.left = 'calc(50% - 60px)';
  ripple.style.top = 'calc(50% - 60px)';
  container.appendChild(ripple);
  ripple.addEventListener('animationend', () => ripple.remove());
}

/* ============================================================
   Events
   ============================================================ */
function bindEvents() {
  $('big-toggle').addEventListener('click', async () => {
    // 3. Spawn ripple
    spawnRipple();
    // 13. Haptic micro-shake (CSS handles this via :active)

    const result = await sendMessage({ type: 'TOGGLE_ENABLED' });
    if (result?.error) {
      flashError(result.error);
    } else {
      // sucesso — esconde aviso anterior, se houver
      const toggleErr = $('toggle-error');
      if (toggleErr) toggleErr.hidden = true;
    }
    settings = result || settings;
    render();
  });

  $('open-options')?.addEventListener('click', () => chrome.runtime.openOptionsPage());

  $('lic-change')?.addEventListener('click', async () => {
    settings = await sendMessage({ type: 'CLEAR_LICENSE' });
    render();
  });

  $('lic-activate').addEventListener('click', activate);
  $('lic-input').addEventListener('keydown', (e) => {
    if (e.key === 'Enter') activate();
  });

  $('download-project')?.addEventListener('click', downloadProject);
  $('download-header')?.addEventListener('click', downloadProject);
  // Chips de sugestao: insere um prompt pronto no campo do chat
  document.querySelectorAll('.suggestion-chip').forEach((chip) => {
    chip.addEventListener('click', () => {
      const ta = $('chat-input');
      if (!ta) return;
      ta.value = chip.getAttribute('data-suggestion') || '';
      ta.dispatchEvent(new Event('input', { bubbles: true }));
      ta.focus();
    });
  });
  $('hide-badge')?.addEventListener('click', hideBadgeViaChat);
  $('publish-project')?.addEventListener('click', publishProject);
  $('publish-header')?.addEventListener('click', publishProject);
  $('create-project-btn')?.addEventListener('click', openCreateProjectModal);
  $('create-modal-close')?.addEventListener('click', closeCreateProjectModal);
  $('create-cancel-btn')?.addEventListener('click', closeCreateProjectModal);
  $('create-confirm-btn')?.addEventListener('click', submitCreateProject);
  $('create-project-modal')?.addEventListener('click', (e) => {
    if (e.target?.classList?.contains('create-modal-backdrop')) closeCreateProjectModal();
  });
  $('create-prompt')?.addEventListener('keydown', (e) => {
    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
      e.preventDefault();
      submitCreateProject();
    }
  });
  $('security-scan')?.addEventListener('click', openSecurityModal);
  $('security-modal-close')?.addEventListener('click', closeSecurityModal);
  $('security-modal')?.addEventListener('click', (e) => {
    if (e.target?.classList?.contains('security-modal-backdrop')) closeSecurityModal();
  });
  $('security-rescan')?.addEventListener('click', rescanSecurity);
  $('security-fix-all')?.addEventListener('click', fixAllSecurity);
  $('chat-send')?.addEventListener('click', sendTryToFix);
  $('chat-approve-plan')?.addEventListener('click', approvePlanViaChat);
  // Botão "Aprovar plano" começa escondido; só aparece quando há plano pendente.
  setApprovePlanVisible(false);
  chrome.runtime.onMessage.addListener((m) => {
    if (m?.type === 'PLAN_STATE_CHANGED') setApprovePlanVisible(!!m.pending);
    if (m?.type === 'LICENSE_REVOKED') {
      // Licença revogada/expirada detectada pelo polling do background.
      // Força reload das settings e re-renderiza → vai pra tela de licença.
      sendMessage({ type: 'GET_SETTINGS' }).then((s) => {
        if (s) { settings = s; render(); }
      }).catch(() => {});
    }
  });
  refreshPlanButton();
  setInterval(refreshPlanButton, 3000);
  $('chat-input')?.addEventListener('keydown', (e) => {
    // Enter envia; Shift+Enter quebra linha (padrão de chats)
    if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
      e.preventDefault();
      sendTryToFix();
    }
  });
  $('chat-input')?.addEventListener('input', () => autoResizeTextarea($('chat-input')));

  bindChatAttachments();
  $('chat-clear-history')?.addEventListener('click', clearChatHistory);
  $('chat-improve')?.addEventListener('click', improvePrompt);
  $('chat-mode-pill')?.addEventListener('click', function(){ setChatMode(!getCurrentChatMode()); });
  document.querySelectorAll('#chat-mode-menu .chat-mode-menu-item').forEach((btn) => {
    btn.addEventListener('click', (e) => {
      const val = e.currentTarget.dataset.value;
      setChatMode(val === 'plan');
      closeChatModeMenu();
    });
  });
  document.addEventListener('click', (e) => {
    const menu = $('chat-mode-menu');
    const pill = $('chat-mode-pill');
    if (!menu || menu.hidden) return;
    if (e.target === pill || pill?.contains(e.target)) return;
    if (e.target === menu || menu.contains(e.target)) return;
    closeChatModeMenu();
  });
  $('theme-toggle')?.addEventListener('click', toggleTheme);
  $('window-mode')?.addEventListener('click', openInWindow);
  bindTabs();

  // 10. Tilt effect on info cards
  document.querySelectorAll('[data-tilt]').forEach(card => {
    card.addEventListener('mousemove', (e) => {
      const rect = card.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const centerX = rect.width / 2;
      const centerY = rect.height / 2;
      const rotateX = ((y - centerY) / centerY) * -4;
      const rotateY = ((x - centerX) / centerX) * 4;
      card.style.transform = `perspective(400px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateY(-2px)`;
    });
    card.addEventListener('mouseleave', () => {
      card.style.transform = '';
    });
  });
}

async function activate() {
  const btn = $('lic-activate');
  const input = $('lic-input');
  const err = $('lic-error');
  const successRow = $('lic-activated-row');
  const key = input.value.trim();
  if (!key) {
    err.hidden = false;
    err.textContent = 'Cole uma chave primeiro';
    return;
  }
  btn.disabled = true;
  btn.querySelector('.btn-text').textContent = 'Validando…';
  err.hidden = true;
  if (successRow) successRow.hidden = true;

  const state = await sendMessage({ type: 'VALIDATE_LICENSE', key });
  settings = await sendMessage({ type: 'GET_SETTINGS' });

  if (state?.status === 'valid') {
    // Mostra mensagem de sucesso e aguarda 900ms antes de trocar de view
    if (successRow) successRow.hidden = false;
    btn.querySelector('.btn-text').textContent = 'Ativar Licença';
    btn.disabled = false;
    await new Promise((r) => setTimeout(r, 900));
    render();
    return;
  }

  render();
  btn.disabled = false;
  btn.querySelector('.btn-text').textContent = 'Ativar Licença';

  if (state?.status !== 'valid') {
    err.hidden = false;
    err.textContent = state?.error || 'Falha ao validar';
    if (successRow) successRow.hidden = true;
  }
}

/* ============================================================
   Helpers
   ============================================================ */
function formatPlan(plan) {
  if (!plan) return '—';
  return ({
    day: 'Diário', week: 'Semanal', month: 'Mensal',
    pro: 'Pro', enterprise: 'Enterprise', free: 'Free',
  })[plan] || plan;
}

function formatExpires(iso) {
  if (!iso) return 'sem expiração';
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return iso;
  const now = new Date();
  const diffMs = d - now;
  if (diffMs <= 0) return 'EXPIRADA';
  const totalSec = Math.floor(diffMs / 1000);
  const days = Math.floor(totalSec / 86400);
  const hours = Math.floor((totalSec % 86400) / 3600);
  const mins = Math.floor((totalSec % 3600) / 60);
  const secs = totalSec % 60;
  const pad = (n) => String(n).padStart(2, '0');
  if (days > 0) return `${days}d ${pad(hours)}:${pad(mins)}:${pad(secs)}`;
  return `${pad(hours)}:${pad(mins)}:${pad(secs)}`;
}

/* ============================================================
   Live Countdown Timer — atualiza a cada segundo
   ============================================================ */
let _countdownInterval = null;
function startCountdown() {
  if (_countdownInterval) clearInterval(_countdownInterval);
  _countdownInterval = setInterval(() => {
    const el = $('lic-expires');
    if (!el) return;
    const iso = settings?.licenseState?.expiresAt;
    if (!iso) return;
    const text = formatExpires(iso);
    el.textContent = text;
    renderExpiryBar();
    if (text === 'EXPIRADA') {
      el.style.color = '#FF3B3B';
      clearInterval(_countdownInterval);
    }
  }, 1000);
}

function flashError(text) {
  // Tenta mostrar no aviso do power toggle (main view), se existir.
  const toggleErr = $('toggle-error');
  const toggleErrText = $('toggle-error-text');
  if (toggleErr && toggleErrText) {
    toggleErrText.textContent = text;
    toggleErr.hidden = false;
    clearTimeout(flashError._t);
    flashError._t = setTimeout(() => { toggleErr.hidden = true; }, 6000);
    return;
  }
  // Fallback: mostra no erro da view de licença
  const err = $('lic-error');
  if (!err) return;
  err.hidden = false;
  err.textContent = text;
  setTimeout(() => { err.hidden = true; }, 3500);
}

function setVersion() {
  try {
    const v = chrome.runtime.getManifest().version;
    const el = $('ver');
    if (el) el.textContent = `v${v}`;
  } catch (_) {}
}

/* ============================================================
   Download do projeto
   ============================================================ */
let _activeProjectId = null;

async function updateDownloadButton() {
  const btn = $('download-project');
  const status = $('download-status');
  if (!btn) return;
  const res = await sendMessage({ type: 'GET_ACTIVE_PROJECT_ID' });
  _activeProjectId = res?.projectId || null;
  const projectHint = $('project-hint');
  if (_activeProjectId) {
    btn.hidden = false;
    if (status) status.hidden = true;
    if (projectHint) projectHint.hidden = true;
    // Topbar buttons
    const dlHeader = $('download-header');
    const pbHeader = $('publish-header');
    const lcChange = $('lic-change');
    if (dlHeader) dlHeader.hidden = false;
    if (pbHeader) pbHeader.hidden = false;
    if (lcChange) lcChange.hidden = false;
  } else {
    btn.hidden = true;
    if (status) status.hidden = true;
    if (projectHint) projectHint.hidden = false;
    // Topbar buttons
    const dlHeader = $('download-header');
    const pbHeader = $('publish-header');
    const lcChange = $('lic-change');
    if (dlHeader) dlHeader.hidden = true;
    if (pbHeader) pbHeader.hidden = true;
    if (lcChange) lcChange.hidden = false; // sempre visível (para trocar licença)
  }
  updateHideBadgeButton();
  updatePublishButton();
  updateSecurityButton();
  updateChatSection();
}

function updatePublishButton() {
  const btn = $('publish-project');
  const status = $('publish-status');
  if (!btn) return;
  btn.hidden = !_activeProjectId;
  if (status && !_activeProjectId) status.hidden = true;
}

function updateSecurityButton() {
  const btn = $('security-scan');
  const status = $('security-status');
  if (!btn) return;
  btn.hidden = !_activeProjectId;
  if (status && !_activeProjectId) status.hidden = true;
}

function updateChatSection() {
  const section = $('chat-section');
  const hint = $('chat-hint');
  if (!section) return;
  section.hidden = !_activeProjectId;
  if (hint) hint.hidden = !!_activeProjectId;
  renderChatHistory();
  renderChatModePill();
}

/* Helper: verifica se há licença válida. Se não, força re-render
 * (mostra a view de ativação) e bloqueia a ação.
 * Aceita uma função opcional de status pra mostrar mensagem no contexto certo. */
function requireValidLicense(setStatusFn) {
  const status = settings?.licenseState?.status;
  if (status === 'valid') return true;
  if (typeof setStatusFn === 'function') {
    try { setStatusFn(settings?.licenseState?.error || 'Licenca invalida', 'error'); } catch (_) {}
  }
  return false;
}

/* Helper: traduz erros de comunicação com a aba em códigos específicos.
 * Códigos:
 *   [E-CONN]  → content script não responde (provável: aba sem content script vivo)
 *   [E-TIMEOUT] → resposta demorou demais
 *   [E-RAW]   → erro genérico (mostra mensagem original) */
function formatTabError(err) {
  const raw = String(err?.message || err || '');
  const m = raw.toLowerCase();
  if (
    m.includes('could not establish connection') ||
    m.includes('receiving end does not exist') ||
    m.includes('the message port closed')
  ) {
    return 'Recarregue a página do Lovable (F5) e envie de novo.';
  }
  if (m.includes('timeout')) {
    return '[E-TIMEOUT] Resposta demorou demais. Tente novamente.';
  }
  return '[E-RAW] ' + (raw || 'Falha desconhecida na comunicação com a aba.');
}

/* Helper: diagnóstico completo do estado atual.
 * Roda quando algum botão dá erro pra mostrar o que está faltando. */
async function diagnose() {
  let tabUrl = '(sem aba)';
  let tabId = null;
  let pingOk = null;
  try {
    const tab = await getLovableTab();
    if (tab) { tabUrl = tab.url || '(sem url)'; tabId = tab.id; }
  } catch (_) {}
  if (tabId) {
    try {
      const pong = await Promise.race([
        chrome.tabs.sendMessage(tabId, { type: 'PING' }),
        new Promise((_, rj) => setTimeout(() => rj(new Error('ping-timeout')), 1500)),
      ]);
      pingOk = !!pong?.ok;
    } catch (e) { pingOk = false; }
  }
  const fresh = await sendMessage({ type: 'GET_SETTINGS' }).catch(() => null);
  const tokenLen = (fresh?.lovableToken || '').length;
  const wsId = fresh?.lovableWorkspaceId || '';
  const lic = fresh?.licenseState?.status || 'unknown';
  return {
    license: lic,
    isLovableTab: /https:\/\/lovable\.dev\/projects\//i.test(tabUrl),
    tabUrl: tabUrl.slice(0, 80),
    tokenLen,
    workspaceId: wsId,
    contentPing: pingOk,
  };
}

/* Helper: mostra erro detalhado com diagnóstico */
async function showDetailedError(setStatusFn, codePrefix, baseMsg) {
  const d = await diagnose();
  const parts = [`[${codePrefix}] ${baseMsg}`];
  parts.push(`license=${d.license} | tabLovable=${d.isLovableTab ? 'sim' : 'NÃO'} | token=${d.tokenLen}B | ws=${d.workspaceId ? 'sim' : 'NÃO'} | ping=${d.contentPing === null ? 'n/a' : (d.contentPing ? 'OK' : 'OFFLINE')}`);
  console.warn('[PULSE diagnose]', d, '| url:', d.tabUrl);
  if (typeof setStatusFn === 'function') {
    setStatusFn(parts.join(' — '), 'error');
  }
}

/* Helper: aguarda o inject.js capturar o Bearer token (lê do firebase IndexedDB).
 * Faz polling no storage por até 15s. Retorna o token ou null. */
// Le o token DIRETO da pagina do Lovable (confiavel — nao depende da corrente
// inject->content->background, que estava falhando). Tenta Firebase
// (localStorage e IndexedDB) e Supabase (localStorage).
async function _tokenFromLovablePage() {
  try {
    const tab = await getLovableTab();
    if (!tab?.id) return null;
    const inj = await chrome.scripting.executeScript({
      target: { tabId: tab.id },
      func: () =>
        new Promise((resolve) => {
          function _fbLS() {
            try {
              for (let i = 0; i < localStorage.length; i++) {
                const k = localStorage.key(i);
                if (k && k.indexOf('firebase:authUser:') === 0) {
                  try {
                    const v = JSON.parse(localStorage.getItem(k));
                    const t =
                      v &&
                      ((v.stsTokenManager && v.stsTokenManager.accessToken) ||
                        v.accessToken);
                    if (t) return t;
                  } catch (e) {}
                }
              }
            } catch (e) {}
            return null;
          }
          function _supaLS() {
            try {
              for (let i = 0; i < localStorage.length; i++) {
                const k = localStorage.key(i);
                if (!k) continue;
                if (
                  k.indexOf('auth-token') >= 0 ||
                  k.indexOf('sb-') >= 0 ||
                  k.indexOf('supabase') >= 0
                ) {
                  try {
                    let raw = localStorage.getItem(k);
                    if (!raw) continue;
                    if (raw.indexOf('base64-') === 0) {
                      try { raw = atob(raw.slice(7)); } catch (e) {}
                    }
                    const v = JSON.parse(raw);
                    if (v && v.access_token) return v.access_token;
                    if (v && v.currentSession && v.currentSession.access_token)
                      return v.currentSession.access_token;
                    if (Array.isArray(v)) {
                      for (const it of v) if (it && it.access_token) return it.access_token;
                    }
                  } catch (e) {}
                }
              }
            } catch (e) {}
            return null;
          }
          const fb = _fbLS();
          if (fb) { resolve(fb); return; }
          let done = false;
          const finish = (t) => {
            if (!done) { done = true; resolve(t || _supaLS()); }
          };
          try {
            const req = indexedDB.open('firebaseLocalStorageDb');
            req.onsuccess = () => {
              try {
                const db = req.result;
                const all = db
                  .transaction('firebaseLocalStorage', 'readonly')
                  .objectStore('firebaseLocalStorage')
                  .getAll();
                all.onsuccess = () => {
                  try {
                    for (const item of all.result || []) {
                      const t =
                        item &&
                        item.value &&
                        item.value.stsTokenManager &&
                        item.value.stsTokenManager.accessToken;
                      if (t) { finish(t); return; }
                    }
                  } catch (e) {}
                  finish(null);
                };
                all.onerror = () => finish(null);
              } catch (e) { finish(null); }
            };
            req.onerror = () => finish(null);
            setTimeout(() => finish(null), 1500);
          } catch (e) { finish(null); }
        }),
    });
    return (inj && inj[0] && inj[0].result) || null;
  } catch (_) {
    return null;
  }
}

async function waitForLovableToken(setStatusFn, maxMs = 15000) {
  if (settings?.lovableToken) return settings.lovableToken;
  // 1) Le direto da pagina (mais confiavel)
  const pageTok = await _tokenFromLovablePage();
  if (pageTok) {
    chrome.runtime.sendMessage({ type: 'SAVE_LOVABLE_TOKEN', token: pageTok });
    return pageTok;
  }
  // 2) Fallback: acorda o inject e espera salvar
  if (typeof setStatusFn === 'function') {
    try { setStatusFn('Conectando ao Lovable…'); } catch (_) {}
  }
  try {
    const tab = await getLovableTab();
    if (tab?.id) {
      chrome.tabs.sendMessage(tab.id, { type: 'WAKE_TOKEN_CAPTURE' }).catch(() => {});
    }
  } catch (_) {}
  const startedAt = Date.now();
  while (Date.now() - startedAt < maxMs) {
    await new Promise((r) => setTimeout(r, 400));
    const again = await _tokenFromLovablePage();
    if (again) {
      chrome.runtime.sendMessage({ type: 'SAVE_LOVABLE_TOKEN', token: again });
      return again;
    }
    const fresh = await sendMessage({ type: 'GET_SETTINGS' });
    if (fresh?.lovableToken) {
      settings = fresh;
      return fresh.lovableToken;
    }
  }
  return null;
}

// Modo do chat por projeto — só na sessão atual.
// Sempre volta para "Construir" quando o painel/extensão recarrega.
const _chatModeByProject = new Map();

function getCurrentChatMode() {
  if (!_activeProjectId) return false;
  return !!_chatModeByProject.get(_activeProjectId);
}

function renderChatModePill() {
  const pill = $('chat-mode-pill');
  const label = $('chat-mode-label');
  if (!pill) return;
  const enabled = getCurrentChatMode();
  pill.dataset.mode = enabled ? 'plan' : 'build';
  if (label) label.textContent = enabled ? 'Modo Plano' : 'Construir';
  pill.title = enabled
    ? 'Modo: Plano (a IA elabora um plano antes de executar)'
    : 'Modo: Construir (a IA modifica o código direto)';

  // Marca o item selecionado no menu
  document.querySelectorAll('#chat-mode-menu .chat-mode-menu-item').forEach((b) => {
    const v = b.dataset.value;
    const isMatch = (enabled && v === 'plan') || (!enabled && v === 'build');
    b.setAttribute('aria-selected', isMatch ? 'true' : 'false');
  });
}

function openChatModeMenu(e) {
  const menu = $('chat-mode-menu');
  if (!menu) return;
  if (!menu.hidden) {
    closeChatModeMenu();
    return;
  }
  menu.hidden = false;
  e?.stopPropagation();
}

function closeChatModeMenu() {
  const menu = $('chat-mode-menu');
  if (menu) menu.hidden = true;
}

async function setChatMode(enabled) {
  if (!requireValidLicense(setChatStatus)) return;
  if (!_activeProjectId) return;
  // Aviso de credito ao LIGAR o Planejar (Construir e gratis)
  if (enabled && !getCurrentChatMode()) {
    const ok = window.confirm('⚠️ O modo PLANEJAR consome créditos da Lovable a cada mensagem.\n\nO modo CONSTRUIR é ilimitado. Use o Planejar só quando precisar de um plano.\n\nQuer ativar o Planejar mesmo assim?');
    if (!ok) { renderChatModePill(); return; }
  }
  // Otimista: atualiza UI imediatamente (só em memória, não persiste)
  _chatModeByProject.set(_activeProjectId, !!enabled);
  renderChatModePill();

  // Sincroniza com a Lovable em background
  const token = settings?.lovableToken;
  if (!token) return;
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) return;
  chrome.tabs.sendMessage(tab.id, {
    type: 'SET_CHAT_MODE',
    projectId: _activeProjectId,
    token,
    enabled: !!enabled,
  }).catch(() => { /* falha silenciosa — UI local já está atualizada */ });
}

function updateHideBadgeButton() {
  const btn = $('hide-badge');
  const txt = $('hide-badge-text');
  const status = $('hide-badge-status');
  if (!btn) return;
  if (!_activeProjectId) {
    btn.hidden = true;
    if (status) status.hidden = true;
    return;
  }
  btn.hidden = false;
  if (status) status.hidden = true;
  const hidden = !!(settings?.hiddenBadgesByProject || {})[_activeProjectId];
  btn.dataset.state = hidden ? 'hidden' : 'visible';
  if (txt) txt.textContent = 'Remover marca Lovable';
}

const _statusTimers = {}; // por elemento id → timeoutId
function _scheduleAutoClear(elId, clearFn) {
  if (_statusTimers[elId]) clearTimeout(_statusTimers[elId]);
  _statusTimers[elId] = setTimeout(() => { try { clearFn(); } catch (_) {} }, 6000);
}
function _cancelAutoClear(elId) {
  if (_statusTimers[elId]) { clearTimeout(_statusTimers[elId]); delete _statusTimers[elId]; }
}

function setDownloadStatus(text, kind) {
  const el = $('download-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('download-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('download-status', () => setDownloadStatus(''));
  else el.removeAttribute('data-kind');
}

async function downloadProject() {
  if (!requireValidLicense(setDownloadStatus)) return;
  const btn = $('download-project');
  const textEl = btn.querySelector('.download-text');
  const iconEl = btn.querySelector('.download-icon');
  const spinnerEl = btn.querySelector('.download-spinner');
  if (!_activeProjectId) {
    setDownloadStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }
  // Spinner já durante a espera do token
  btn.disabled = true;
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  const token = await waitForLovableToken(setDownloadStatus);
  if (!token) {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
    setDownloadStatus('Recarregue a página do Lovable.', 'error');
    return;
  }

  // Encontra a aba ativa do Lovable — o fetch precisa rodar no content script
  // pra herdar a origem lovable.dev e evitar bloqueio CORS do api.lovable.dev.
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
    await showDetailedError(setDownloadStatus, 'E-NO-TAB', 'Aba ativa não é projeto Lovable.');
    return;
  }

  const originalText = textEl.textContent;
  textEl.textContent = 'Baixando…';
  setDownloadStatus('Conectando ao Lovable…');

  try {
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'DOWNLOAD_PROJECT',
      projectId: _activeProjectId,
      token,
    });
    if (res?.ok) {
      setDownloadStatus(`Baixado: ${res.filename}`, 'ok');
      setTimeout(() => setDownloadStatus(''), 4000);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403 || /401|403/.test(errMsg)) {
        await showDetailedError(setDownloadStatus, 'E-AUTH', 'Token rejeitado (401/403). F5 na aba pra renovar.');
      } else {
        setDownloadStatus('[E-HTTP] ' + errMsg, 'error');
      }
    }
  } catch (e) {
    console.error('[PULSE popup] download dispatch failed:', e);
    setDownloadStatus(formatTabError(e), 'error');
  } finally {
    btn.disabled = false;
    iconEl.hidden = false;
    spinnerEl.hidden = true;
    textEl.textContent = originalText;
  }
}

/* ============================================================
   Ocultar marca Lovable — envia CSS via chat
   ============================================================ */
async function hideBadgeViaChat() {
  if (!requireValidLicense(setHideBadgeStatus)) return;
  if (!_activeProjectId) {
    setHideBadgeStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }

  const HIDE_MSG = `Adicione esse c\u00f3digo no final do c\u00f3digo do index.css:\n\n#lovable-badge {\n  display: none !important;\n}`;

  const btn = $('hide-badge');
  if (btn) btn.disabled = true;
  setHideBadgeStatus('Enviando no chat\u2026');

  try {
    const token = await waitForLovableToken(setHideBadgeStatus);
    if (!token) {
      if (btn) btn.disabled = false;
      return;
    }
    const tab = await getLovableTab();
    if (!tab?.id) {
      setHideBadgeStatus('Abra o projeto no Lovable primeiro.', 'error');
      if (btn) btn.disabled = false;
      return;
    }
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'SEND_TRY_TO_FIX',
      projectId: _activeProjectId,
      token,
      text: HIDE_MSG,
      attachments: [],
      chatMode: false,
      sessionId: settings?.lovableSessionId || '',
      gitSha: settings?.lovableClientGitSha || '',
    });
    if (res?.ok) {
      setHideBadgeStatus('\u2713 Marca removida com sucesso!', 'ok');
      const entry = { text: HIDE_MSG, at: Date.now(), ok: true };
      await appendChatHistory(entry);
      setTimeout(() => setHideBadgeStatus(''), 3500);
    } else {
      setHideBadgeStatus('[Erro] ' + (res?.error || 'falha ao enviar'), 'error');
    }
  } catch (e) {
    setHideBadgeStatus('[Erro] ' + e.message, 'error');
  } finally {
    if (btn) btn.disabled = false;
  }
}

async function toggleLovableBadge() {
  if (!requireValidLicense(setHideBadgeStatus)) return;
  const btn = $('hide-badge');
  const txt = $('hide-badge-text');
  const iconEl = btn.querySelector('.download-icon');
  const spinnerEl = btn.querySelector('.download-spinner');
  if (!_activeProjectId) {
    setHideBadgeStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }
  // Spinner já durante a espera do token
  btn.disabled = true;
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  const _resetHideBtn = () => {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
  };
  const token = await waitForLovableToken(setHideBadgeStatus);
  if (!token) {
    _resetHideBtn();
    setHideBadgeStatus('Recarregue a página do Lovable.', 'error');
    return;
  }
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    _resetHideBtn();
    await showDetailedError(setHideBadgeStatus, 'E-NO-TAB', 'Aba ativa não é projeto Lovable.');
    return;
  }

  const currentlyHidden = !!(settings?.hiddenBadgesByProject || {})[_activeProjectId];
  const nextHidden = !currentlyHidden;

  const originalText = txt.textContent;
  txt.textContent = nextHidden ? 'Ocultando…' : 'Restaurando…';
  setHideBadgeStatus('Conectando ao Lovable…');

  try {
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'HIDE_LOVABLE_BADGE',
      projectId: _activeProjectId,
      token,
      hide: nextHidden,
    });
    if (res?.ok) {
      const map = { ...(settings?.hiddenBadgesByProject || {}) };
      map[_activeProjectId] = !!res.hide_badge;
      const updated = await sendMessage({ type: 'SET_SETTINGS', updates: { hiddenBadgesByProject: map } });
      if (updated) settings = updated;
      setHideBadgeStatus(res.hide_badge ? '✓ Marca d\'água oculta no projeto' : '✓ Marca d\'água restaurada', 'ok');
      setTimeout(() => setHideBadgeStatus(''), 4000);
      updateHideBadgeButton();
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403 || /401|403/.test(errMsg)) {
        await showDetailedError(setHideBadgeStatus, 'E-AUTH', 'Token rejeitado (401/403). F5 na aba pra renovar.');
      } else if (res?.status === 429 || /429|rate.?limit|high demand/i.test(errMsg)) {
        setHideBadgeStatus('[E-429] Muitas tentativas seguidas. Aguarde alguns segundos.', 'error');
      } else if (res?.status === 402 || /402|paid|payment|premium|plan/i.test(errMsg)) {
        setHideBadgeStatus('[E-402] Recurso disponível só em planos pagos da Lovable.', 'error');
      } else {
        setHideBadgeStatus('[E-HTTP] ' + errMsg, 'error');
      }
      txt.textContent = originalText;
    }
  } catch (e) {
    console.error('[PULSE popup] hide badge dispatch failed:', e);
    setHideBadgeStatus(formatTabError(e), 'error');
    txt.textContent = originalText;
  } finally {
    btn.disabled = false;
    iconEl.hidden = false;
    spinnerEl.hidden = true;
  }
}

function setHideBadgeStatus(text, kind) {
  const el = $('hide-badge-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('hide-badge-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('hide-badge-status', () => setHideBadgeStatus(''));
  else el.removeAttribute('data-kind');
}

/* ============================================================
   Publicar projeto
   ============================================================ */
async function publishProject() {
  if (!requireValidLicense(setPublishStatus)) return;
  const btn = $('publish-project');
  const textEl = btn.querySelector('.download-text');
  const iconEl = btn.querySelector('.download-icon');
  const spinnerEl = btn.querySelector('.download-spinner');
  if (!_activeProjectId) {
    setPublishStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }
  // Spinner já durante a espera do token
  btn.disabled = true;
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  const _resetPubBtn = () => {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
  };
  const token = await waitForLovableToken(setPublishStatus);
  if (!token) {
    _resetPubBtn();
    setPublishStatus('Recarregue a página do Lovable.', 'error');
    return;
  }
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    _resetPubBtn();
    await showDetailedError(setPublishStatus, 'E-NO-TAB', 'Aba ativa não é projeto Lovable.');
    return;
  }
  const originalText = textEl.textContent;
  textEl.textContent = 'Publicando…';
  setPublishStatus('Disparando deploy no Lovable…');

  try {
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'PUBLISH_PROJECT',
      projectId: _activeProjectId,
      token,
    });
    if (res?.ok) {
      setPublishStatus('✓ Deploy iniciado. O Lovable está publicando seu projeto.', 'ok');
      setTimeout(() => setPublishStatus(''), 6000);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403 || /401|403/.test(errMsg)) {
        await showDetailedError(setPublishStatus, 'E-AUTH', 'Token rejeitado (401/403). F5 na aba pra renovar.');
      } else if (res?.status === 429 || /429|rate.?limit|high demand/i.test(errMsg)) {
        setPublishStatus('[E-429] Muitas tentativas seguidas. Aguarde alguns segundos.', 'error');
      } else if (res?.status === 402 || /402|paid|payment|premium|plan/i.test(errMsg)) {
        setPublishStatus('[E-402] Recurso disponível só em planos pagos da Lovable.', 'error');
      } else {
        setPublishStatus('[E-HTTP] ' + errMsg, 'error');
      }
    }
  } catch (e) {
    console.error('[PULSE popup] publish dispatch failed:', e);
    setPublishStatus(formatTabError(e), 'error');
  } finally {
    btn.disabled = false;
    iconEl.hidden = false;
    spinnerEl.hidden = true;
    textEl.textContent = originalText;
  }
}

function setPublishStatus(text, kind) {
  const el = $('publish-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('publish-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('publish-status', () => setPublishStatus(''));
  else el.removeAttribute('data-kind');
}

/* ============================================================
   Criar Novo Projeto
   ============================================================ */
let _createProjectKeyHandler = null;

function openCreateProjectModal() {
  if (!requireValidLicense()) return;
  const modal = $('create-project-modal');
  if (!modal) return;
  modal.hidden = false;
  const input = $('create-prompt');
  if (input) setTimeout(() => input.focus(), 0);
  setCreateStatus('');

  // Dispara captura proativa do castle_token enquanto o user digita,
  // pra ter tempo do SDK do Castle gerar o token (pode levar 1-2s).
  (async () => {
    try {
      const tab = await getLovableTab();
      if (tab?.id) {
        chrome.tabs.sendMessage(tab.id, { type: 'WAKE_TOKEN_CAPTURE' }).catch(() => {});
      }
    } catch (_) {}
  })();

  _createProjectKeyHandler = (e) => {
    if (e.key === 'Escape') {
      e.preventDefault();
      closeCreateProjectModal();
    }
  };
  document.addEventListener('keydown', _createProjectKeyHandler, true);
}

function closeCreateProjectModal() {
  const modal = $('create-project-modal');
  if (!modal) return;
  modal.hidden = true;
  if (_createProjectKeyHandler) {
    document.removeEventListener('keydown', _createProjectKeyHandler, true);
    _createProjectKeyHandler = null;
  }
}

function setCreateStatus(text, kind) {
  const el = $('create-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('create-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('create-status', () => setCreateStatus(''));
  else el.removeAttribute('data-kind');
}

async function submitCreateProject() {
  if (!requireValidLicense(setCreateStatus)) return;
  const btn = $('create-confirm-btn');
  const textEl = btn?.querySelector('.create-btn-text');
  const spinnerEl = btn?.querySelector('.create-btn-spinner');
  const input = $('create-prompt');
  const planMode = $('create-plan-mode')?.checked || false;
  const prompt = (input?.value || '').trim();

  if (!prompt) {
    setCreateStatus('Descreva o projeto que quer criar.', 'error');
    input?.focus();
    return;
  }

  // Spinner já durante a espera do token
  btn.disabled = true;
  if (spinnerEl) spinnerEl.hidden = false;
  const _resetCreateBtn = () => {
    btn.disabled = false;
    if (spinnerEl) spinnerEl.hidden = true;
    if (textEl) textEl.textContent = 'Criar projeto';
  };

  const token = await waitForLovableToken(setCreateStatus);
  let workspaceId = settings?.lovableWorkspaceId;
  if (!token) {
    _resetCreateBtn();
    setCreateStatus('Recarregue a página do Lovable.', 'error');
    return;
  }

  // Precisa de uma aba Lovable ativa pra rodar o fetch via content script
  let tab = null;
  {
    const all = await chrome.tabs.query({ url: 'https://lovable.dev/*' });
    tab = all?.find((t) => t.active && t.currentWindow) || all?.[0];
  }
  if (!tab?.id) {
    _resetCreateBtn();
    await showDetailedError(setCreateStatus, 'E-NO-TAB', 'Nenhuma aba lovable.dev aberta. Abra uma e tente novamente.');
    return;
  }

  // Se o workspace_id não foi capturado passivamente, busca via /user/workspaces
  if (!workspaceId) {
    setCreateStatus('Detectando workspace…');
    try {
      const wsRes = await chrome.tabs.sendMessage(tab.id, {
        type: 'FETCH_WORKSPACE_ID',
        token,
        sessionId: settings?.lovableSessionId || '',
        gitSha: settings?.lovableClientGitSha || '',
      });
      console.log('[PULSE popup] FETCH_WORKSPACE_ID →', wsRes);
      if (wsRes?.ok && wsRes.workspaceId) {
        workspaceId = wsRes.workspaceId;
        const fresh = await sendMessage({ type: 'GET_SETTINGS' });
        if (fresh) settings = fresh;
      } else {
        _resetCreateBtn();
        await showDetailedError(setCreateStatus, 'E-NO-WS', wsRes?.error || 'Workspace não detectado.');
        return;
      }
    } catch (e) {
      _resetCreateBtn();
      await showDetailedError(setCreateStatus, 'E-NO-WS', formatTabError(e));
      return;
    }
  }

  if (textEl) textEl.textContent = 'Criando…';
  setCreateStatus('Pedindo pra Lovable criar o projeto…');

  try {
    // Garante captura do castle_token (anti-bot da Lovable) — espera até 8s
    // chamando WAKE periodicamente pra forçar inject.js a gerar via SDK
    let castle = settings?.lovableCastleToken || '';
    if (!castle) {
      setCreateStatus('Gerando token anti-bot…');
      const startedAt = Date.now();
      while (Date.now() - startedAt < 8000) {
        try { chrome.tabs.sendMessage(tab.id, { type: 'WAKE_TOKEN_CAPTURE' }).catch(() => {}); } catch (_) {}
        await new Promise((r) => setTimeout(r, 600));
        const fresh = await sendMessage({ type: 'GET_SETTINGS' });
        if (fresh?.lovableCastleToken) {
          settings = fresh;
          castle = fresh.lovableCastleToken;
          break;
        }
      }
    }
    // Re-lê fresh pra pegar session/sha mais recentes
    const fresh = await sendMessage({ type: 'GET_SETTINGS' });
    if (fresh) settings = fresh;
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'CREATE_PROJECT',
      token,
      workspaceId,
      prompt,
      planMode,
      castleToken: settings?.lovableCastleToken || '',
      sessionId: settings?.lovableSessionId || '',
      gitSha: settings?.lovableClientGitSha || '',
    });
    if (res?.ok && res.link) {
      setCreateStatus('✓ Projeto criado! Abrindo…', 'ok');
      setTimeout(() => {
        chrome.tabs.create({ url: res.link });
        closeCreateProjectModal();
        if (input) input.value = '';
        if (textEl) textEl.textContent = 'Criar projeto';
      }, 800);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403) {
        const f = await sendMessage({ type: 'GET_SETTINGS' });
        const castle = (f?.lovableCastleToken || '').length;
        const sess = (f?.lovableSessionId || '').length;
        const sha = (f?.lovableClientGitSha || '').length;
        const extra = `castle=${castle}B sess=${sess}B sha=${sha}B`;
        console.warn('[PULSE create] 401/403 detail:', errMsg, '| extra headers:', extra);
        setCreateStatus(`[E-AUTH] Token rejeitado. ${extra} — ${errMsg.slice(0, 100)}`, 'error');
      } else if (res?.status === 429 || /429|rate.?limit|high demand|try again/i.test(errMsg)) {
        setCreateStatus('[E-429] Muitas tentativas seguidas. Aguarde alguns segundos e tente de novo.', 'error');
      } else if (res?.status === 402 || /402|paid|payment|plan/i.test(errMsg)) {
        setCreateStatus('[E-402] Créditos insuficientes ou recurso premium.', 'error');
      } else if (res?.status === 404 && /workspace_not_found|workspace.+not.+found/i.test(errMsg)) {
        // Workspace_id stale. Limpa storage e re-busca via /user/workspaces, excluindo o inválido.
        console.warn('[PULSE popup] workspace_not_found — wsId enviado:', workspaceId, '— limpando e re-buscando');
        await sendMessage({ type: 'SET_SETTINGS', updates: { lovableWorkspaceId: '' } });
        setCreateStatus('Workspace inválido. Re-detectando…');
        try {
          const wsRes2 = await chrome.tabs.sendMessage(tab.id, {
            type: 'FETCH_WORKSPACE_ID',
            token,
            sessionId: settings?.lovableSessionId || '',
            gitSha: settings?.lovableClientGitSha || '',
            excludeWorkspaceId: workspaceId,
          });
          console.log('[PULSE popup] re-FETCH_WORKSPACE_ID →', wsRes2);
          if (wsRes2?.ok && wsRes2.workspaceId && wsRes2.workspaceId !== workspaceId) {
            const fresh = await sendMessage({ type: 'GET_SETTINGS' });
            if (fresh) settings = fresh;
            const retry = await chrome.tabs.sendMessage(tab.id, {
              type: 'CREATE_PROJECT',
              token,
              workspaceId: wsRes2.workspaceId,
              prompt,
              planMode,
              castleToken: settings?.lovableCastleToken || '',
              sessionId: settings?.lovableSessionId || '',
              gitSha: settings?.lovableClientGitSha || '',
            });
            if (retry?.ok && retry.link) {
              setCreateStatus('✓ Projeto criado! Abrindo…', 'ok');
              setTimeout(() => {
                chrome.tabs.create({ url: retry.link });
                closeCreateProjectModal();
                if (input) input.value = '';
                if (textEl) textEl.textContent = 'Criar projeto';
              }, 800);
              return;
            }
            setCreateStatus('[E-HTTP] Retry falhou: ' + String(retry?.error || 'desconhecido'), 'error');
          } else {
            setCreateStatus(`[E-WS-INVALID] ${wsRes2?.error || 'Workspace inválido. Abra lovable.dev/dashboard e tente novamente.'}`, 'error');
          }
        } catch (e) {
          setCreateStatus(`[E-WS-INVALID] Falha ao re-detectar workspace: ${e?.message || e}`, 'error');
        }
      } else {
        setCreateStatus('[E-HTTP] ' + errMsg, 'error');
      }
      if (textEl) textEl.textContent = 'Criar projeto';
    }
  } catch (e) {
    console.error('[PULSE popup] create project dispatch failed:', e);
    setCreateStatus(formatTabError(e), 'error');
    if (textEl) textEl.textContent = 'Criar projeto';
  } finally {
    btn.disabled = false;
    if (spinnerEl) spinnerEl.hidden = true;
  }
}

/* ============================================================
   Análise de Segurança
   ============================================================ */
let _securityKeyHandler = null;

async function openSecurityModal() {
  if (!requireValidLicense()) return;
  if (!_activeProjectId) return;

  // Abre o modal IMEDIATAMENTE em estado "loading" — o loadSecurityFindings
  // faz waitForLovableToken por dentro e cobre a UI durante a espera.
  const modal = $('security-modal');
  if (!modal) return;
  modal.hidden = false;

  // Esc fecha
  _securityKeyHandler = (e) => {
    if (e.key === 'Escape') {
      e.preventDefault();
      closeSecurityModal();
    }
  };
  document.addEventListener('keydown', _securityKeyHandler, true);

  renderSecurityState('loading');
  await loadSecurityFindings();
}

function closeSecurityModal() {
  const modal = $('security-modal');
  if (!modal) return;
  modal.hidden = true;
  if (_securityKeyHandler) {
    document.removeEventListener('keydown', _securityKeyHandler, true);
    _securityKeyHandler = null;
  }
}

let _lastSecurityFindings = []; // findings achatados, p/ usar no "Corrigir Tudo"

async function loadSecurityFindings() {
  const token = await waitForLovableToken(null);
  if (!token) {
    renderSecurityState('error', 'Recarregue a página do Lovable.');
    return;
  }
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    renderSecurityState('error', `[E-NO-TAB] Aba ativa não é projeto Lovable: ${(tab?.url || '').slice(0, 80)}`);
    return;
  }
  try {
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('timeout-15s')), 15000)
    );
    const msgPromise = chrome.tabs.sendMessage(tab.id, {
      type: 'GET_SECURITY_DATA',
      projectId: _activeProjectId,
      token,
    });
    const res = await Promise.race([msgPromise, timeoutPromise]);
    if (!res?.ok) {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403) {
        renderSecurityState('error', '[E-AUTH] Token rejeitado (401/403). F5 na aba pra renovar.');
      } else {
        renderSecurityState('error', '[E-HTTP] ' + errMsg);
      }
      return;
    }
    // 1) Renderiza em inglês imediatamente pra o usuário ver algo
    renderSecurityFindings(res.data);

    // 2) Em background, traduz e re-renderiza em português (com indicador)
    if (_lastSecurityFindings && _lastSecurityFindings.length > 0) {
      showTranslatingBadge(true);
      try {
        const translated = await translateFindings(_lastSecurityFindings);
        // Só re-renderiza se o modal ainda estiver aberto e os findings forem os mesmos
        const modal = $('security-modal');
        if (modal && !modal.hidden && translated && translated.length === _lastSecurityFindings.length) {
          _lastSecurityFindings = translated;
          renderFindingsList(translated);
        }
      } catch (_) { /* falhou — mantém inglês */ }
      showTranslatingBadge(false);
    }
  } catch (e) {
    console.error('[PULSE popup] security load failed:', e);
    renderSecurityState('error', formatTabError(e));
  }
}

function showTranslatingBadge(show) {
  const summary = $('security-summary');
  if (!summary) return;
  let badge = summary.querySelector('.translating-badge');
  if (show) {
    if (!badge) {
      badge = document.createElement('span');
      badge.className = 'translating-badge';
      badge.innerHTML = '<span class="tb-spinner"></span><span>Traduzindo…</span>';
      summary.appendChild(badge);
    }
  } else if (badge) {
    badge.remove();
  }
}

async function rescanSecurity() {
  const btn = $('security-rescan');
  const textEl = btn?.querySelector('.security-rescan-text');
  const iconEl = btn?.querySelector('.security-rescan-icon');
  const spinnerEl = btn?.querySelector('.security-rescan-spinner');
  if (!btn || !_activeProjectId) return;
  const token = settings?.lovableToken;
  if (!token) return;
  const tab = await getLovableTab();
  if (!tab?.id) return;

  btn.disabled = true;
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  if (textEl) textEl.textContent = 'Analisando…';
  renderSecurityState('loading', 'Disparando nova análise…');

  try {
    const trigger = await chrome.tabs.sendMessage(tab.id, {
      type: 'RUN_SECURITY_SCAN',
      projectId: _activeProjectId,
      token,
      force: true,
    });
    if (!trigger?.ok) {
      renderSecurityState('error', 'Falha ao iniciar análise: ' + (trigger?.error || ''));
      return;
    }
    // Aguarda ~5s e re-busca os resultados
    renderSecurityState('loading', 'Análise iniciada. Aguardando resultados…');
    await new Promise((r) => setTimeout(r, 5000));
    await loadSecurityFindings();
  } catch (e) {
    renderSecurityState('error', 'Falha: ' + (e?.message || e));
  } finally {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
    if (textEl) textEl.textContent = 'Reanalisar agora';
  }
}

function renderSecurityState(state, msg) {
  const body = $('security-body');
  const summary = $('security-summary');
  const fixAllBtn = $('security-fix-all');
  if (!body) return;
  body.innerHTML = '';
  if (summary) { summary.hidden = true; summary.innerHTML = ''; }
  if (fixAllBtn) fixAllBtn.hidden = true;

  if (state === 'loading') {
    const wrap = document.createElement('div');
    wrap.className = 'security-loading';
    const sp = document.createElement('div');
    sp.className = 'sl-spinner';
    const txt = document.createElement('div');
    txt.textContent = msg || 'Carregando análise…';
    wrap.appendChild(sp);
    wrap.appendChild(txt);
    body.appendChild(wrap);
  } else if (state === 'error') {
    const wrap = document.createElement('div');
    wrap.className = 'security-error';
    wrap.textContent = msg || 'Falha ao carregar.';
    body.appendChild(wrap);
  } else if (state === 'empty') {
    const wrap = document.createElement('div');
    wrap.className = 'security-empty';
    wrap.textContent = msg || 'Nenhum achado de segurança. ✓';
    body.appendChild(wrap);
  }
}

function renderSecurityFindings(data) {
  const body = $('security-body');
  const summary = $('security-summary');
  const fixAllBtn = $('security-fix-all');
  if (!body) return;
  body.innerHTML = '';
  if (summary) summary.innerHTML = '';

  // Achata findings de todos os scanners em um array único, com scanner_name junto
  const allFindings = [];
  const results = data?.results || {};
  for (const [scannerKey, scannerData] of Object.entries(results)) {
    const findings = scannerData?.findings || [];
    for (const f of findings) {
      allFindings.push({ ...f, scanner: scannerData?.scanner_name || scannerKey });
    }
  }

  _lastSecurityFindings = allFindings; // guarda pra usar no "Corrigir Tudo"

  // Sumário por nível
  const counts = { error: 0, warn: 0, info: 0 };
  for (const f of allFindings) {
    const lvl = (f.level || 'info').toLowerCase();
    if (counts[lvl] !== undefined) counts[lvl]++;
    else counts.info++;
  }

  if (allFindings.length === 0) {
    if (fixAllBtn) fixAllBtn.hidden = true;
    renderSecurityState('empty', 'Tudo certo! Nenhum achado de segurança encontrado.');
    return;
  }

  if (fixAllBtn) fixAllBtn.hidden = false;

  // Renderiza badges de resumo
  if (summary) {
    summary.hidden = false;
    const order = ['error', 'warn', 'info'];
    const label = { error: 'erros', warn: 'avisos', info: 'info' };
    for (const lvl of order) {
      if (counts[lvl] === 0) continue;
      const b = document.createElement('span');
      b.className = 'sev-badge';
      b.dataset.level = lvl;
      const dot = document.createElement('span');
      dot.className = 'sev-dot';
      b.appendChild(dot);
      const t = document.createElement('span');
      t.textContent = `${counts[lvl]} ${label[lvl]}`;
      b.appendChild(t);
      summary.appendChild(b);
    }
  }

  renderFindingsList(allFindings);
}

function renderFindingsList(findings) {
  const body = $('security-body');
  if (!body) return;
  body.innerHTML = '';

  // Ordena: errors primeiro, depois warns, depois info
  const sevWeight = { error: 0, warn: 1, info: 2 };
  const sorted = [...findings].sort((a, b) => {
    const wa = sevWeight[(a.level || 'info').toLowerCase()] ?? 2;
    const wb = sevWeight[(b.level || 'info').toLowerCase()] ?? 2;
    return wa - wb;
  });

  const ul = document.createElement('ul');
  ul.className = 'findings-list';

  for (const f of sorted) {
    const li = document.createElement('li');
    li.className = 'finding-item';
    li.dataset.level = (f.level || 'info').toLowerCase();

    const header = document.createElement('div');
    header.className = 'finding-header';

    const sev = document.createElement('span');
    sev.className = 'sev-badge';
    sev.dataset.level = (f.level || 'info').toLowerCase();
    const dot = document.createElement('span');
    dot.className = 'sev-dot';
    sev.appendChild(dot);
    const sevText = document.createElement('span');
    sevText.textContent = (f.level || 'info').toUpperCase();
    sev.appendChild(sevText);
    header.appendChild(sev);

    const name = document.createElement('span');
    name.className = 'finding-name';
    name.textContent = f.name || f.id || 'Achado sem título';
    header.appendChild(name);

    if (f.scanner) {
      const sc = document.createElement('span');
      sc.className = 'finding-scanner';
      sc.textContent = f.scanner;
      header.appendChild(sc);
    }

    li.appendChild(header);

    if (f.description) {
      const p = document.createElement('p');
      p.className = 'finding-desc';
      // Remove o " Remediation: <url>" do final pra não duplicar com o link
      p.textContent = String(f.description).replace(/\s*Remediation:\s*https?:\/\/\S+/i, '').trim();
      li.appendChild(p);
    }

    if (f.link) {
      const a = document.createElement('a');
      a.className = 'finding-link';
      a.href = f.link;
      a.target = '_blank';
      a.rel = 'noopener noreferrer';
      a.textContent = 'Ver como corrigir →';
      a.addEventListener('click', (e) => {
        e.preventDefault();
        chrome.tabs.create({ url: f.link });
      });
      li.appendChild(a);
    }

    ul.appendChild(li);
  }

  body.appendChild(ul);
}

async function fixAllSecurity() {
  if (!requireValidLicense()) return;
  const btn = $('security-fix-all');
  const textEl = btn?.querySelector('.security-fixall-text');
  const iconEl = btn?.querySelector('.security-fixall-icon');
  const spinnerEl = btn?.querySelector('.security-fixall-spinner');
  if (!btn || !_activeProjectId) return;
  if (!_lastSecurityFindings || _lastSecurityFindings.length === 0) return;

  const token = settings?.lovableToken;
  if (!token) return;
  const tab = await getLovableTab();
  if (!tab?.id) return;

  btn.disabled = true;
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  const originalText = textEl?.textContent || 'Corrigir Tudo';
  if (textEl) textEl.textContent = 'Enviando…';

  let success = false;
  try {
    // Timeout de 30s pra evitar "Enviando…" travado se a Lovable demorar
    const timeoutPromise = new Promise((_, reject) =>
      setTimeout(() => reject(new Error('timeout-30s')), 30000)
    );
    const msgPromise = chrome.tabs.sendMessage(tab.id, {
      type: 'FIX_ALL_SECURITY',
      projectId: _activeProjectId,
      token,
      findings: _lastSecurityFindings,
    });
    const res = await Promise.race([msgPromise, timeoutPromise]);

    if (res?.ok) {
      success = true;
      if (textEl) textEl.textContent = '✓ Enviado!';
      setTimeout(() => {
        closeSecurityModal();
        if (textEl) textEl.textContent = originalText;
      }, 1800);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403) {
        renderSecurityState('error', '[E-AUTH] Token rejeitado (401/403). F5 na aba pra renovar.');
      } else {
        renderSecurityState('error', '[E-HTTP] Falha ao corrigir: ' + errMsg);
      }
    }
  } catch (e) {
    console.error('[PULSE popup] fix all dispatch failed:', e);
    const m = (e?.message || '').toLowerCase();
    if (m.includes('timeout')) {
      renderSecurityState('error',
        'Tempo esgotado (30s). A Lovable pode ter recebido mesmo assim — veja o chat do projeto.');
    } else {
      renderSecurityState('error', formatTabError(e));
    }
  } finally {
    btn.disabled = false;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
    // Restaura texto SEMPRE (exceto no sucesso, que tem setTimeout próprio)
    if (!success && textEl) textEl.textContent = originalText;
  }
}

/* ============================================================
   Tradução dos findings de segurança via Pollinations.ai
   Cada finding é traduzido individualmente em paralelo (concorrência limitada)
   pra evitar JSON truncado/inconsistente em batches grandes.
   ============================================================ */
const _translationCache = new Map(); // chave = id do finding → { name, description }

/** Traduz texto bruto via Pollinations.ai. Sem dependência de JSON.
 *  Retorna null em erro/falha. */
async function translateText(text) {
  if (!text || typeof text !== 'string') return null;
  const input = String(text).replace(/\s*Remediation:\s*https?:\/\/\S+/i, '').trim();
  if (!input) return null;

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 20000);
  try {
    const res = await fetch('https://text.pollinations.ai/openai', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      signal: controller.signal,
      body: JSON.stringify({
        model: 'openai',
        messages: [
          {
            role: 'system',
            content:
              'Traduza o texto do usuário para português brasileiro. ' +
              'Mantenha em inglês os termos técnicos: RLS, SECURITY DEFINER, SECURITY INVOKER, SELECT, INSERT, UPDATE, DELETE, JOIN, WHERE, USING, WITH CHECK, RPC, JWT, API, OAuth, JWT. ' +
              'Mantenha INALTERADO qualquer trecho dentro de crases (`...`) — são nomes de funções, tabelas, colunas ou arquivos. ' +
              'Mantenha INALTERADAS URLs. ' +
              'Responda APENAS com o texto traduzido — sem aspas no início/fim, sem "Tradução:", sem markdown extra, sem comentários.',
          },
          { role: 'user', content: input },
        ],
        temperature: 0.2,
      }),
    });
    clearTimeout(timeoutId);
    if (!res.ok) {
      console.warn('[PULSE popup] translate HTTP', res.status);
      return null;
    }
    const data = await res.json();
    let out = (data?.choices?.[0]?.message?.content || '').trim();
    // Limpa aspas/markdown que a IA pode ter inserido
    out = out.replace(/^["'`]+|["'`]+$/g, '').trim();
    out = out.replace(/^Tradução:\s*/i, '').trim();
    if (!out) return null;
    return out;
  } catch (e) {
    clearTimeout(timeoutId);
    console.warn('[PULSE popup] translateText failed:', e?.message || e);
    return null;
  }
}

async function translateOneFinding(finding) {
  const hasName = finding.name && finding.name.trim();
  const hasDesc = finding.description && finding.description.trim();
  if (!hasName && !hasDesc) return null;

  const [name, description] = await Promise.all([
    hasName ? translateText(finding.name) : Promise.resolve(null),
    hasDesc ? translateText(finding.description) : Promise.resolve(null),
  ]);

  if (!name && !description) return null;
  return {
    name: name || finding.name,
    description: description || finding.description,
  };
}

async function translateFindings(findings) {
  if (!Array.isArray(findings) || findings.length === 0) return findings;

  // Identifica quais ainda não foram traduzidos
  const toDo = [];
  for (const f of findings) {
    const key = f.id || f.internal_id;
    if (!_translationCache.has(key)) toDo.push({ key, f });
  }

  if (toDo.length > 0) {
    const CONCURRENCY = 3;
    for (let i = 0; i < toDo.length; i += CONCURRENCY) {
      const batch = toDo.slice(i, i + CONCURRENCY);
      await Promise.all(
        batch.map(async ({ key, f }) => {
          // Retry simples: até 2 tentativas
          let tr = await translateOneFinding(f);
          if (!tr) tr = await translateOneFinding(f);
          if (tr) _translationCache.set(key, tr);
        })
      );

      // Re-render progressivo: a cada batch, atualiza a UI com o que já temos
      const modal = $('security-modal');
      if (modal && !modal.hidden) {
        const partial = applyTranslationCache(findings);
        renderFindingsList(partial);
      }
    }
  }

  return applyTranslationCache(findings);
}

function applyTranslationCache(findings) {
  return findings.map((f) => {
    const key = f.id || f.internal_id;
    const tr = _translationCache.get(key);
    if (tr) return { ...f, name: tr.name, description: tr.description, _translated: true };
    return f;
  });
}

function setSecurityStatus(text, kind) {
  const el = $('security-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('security-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('security-status', () => setSecurityStatus(''));
  else el.removeAttribute('data-kind');
}

/* ============================================================
   Chat Pulse Coding
   ============================================================ */
function renderChatHistory() {
  const ul = $('chat-history');
  const wrap = $('chat-history-wrap');
  const empty = $('chat-empty-state');
  const countEl = $('chat-history-count');
  const clearBtn = $('chat-clear-history');
  if (!ul) return;

  const all = settings?.tryToFixHistory || {};
  const items = _activeProjectId ? (all[_activeProjectId] || []) : [];

  if (countEl) countEl.textContent = String(items.length);
  if (wrap) wrap.hidden = items.length === 0;
  if (empty) empty.hidden = items.length > 0;
  if (clearBtn) clearBtn.hidden = items.length === 0;

  if (!_activeProjectId) {
    ul.innerHTML = '';
    return;
  }

  ul.innerHTML = '';
  for (const it of items) {
    const li = document.createElement('li');
    if (it.ok === false) li.dataset.kind = 'error';

    // Rótulo de papel (você / IA)
    if (it.role) {
      const role = document.createElement('span');
      role.className = 'msg-role';
      role.textContent = it.role === 'assistant' ? 'IA' : 'Você';
      li.appendChild(role);
    }

    // Texto da mensagem
    const text = document.createElement('span');
    text.className = 'msg-text';
    text.textContent = it.text || (it.attachments?.length ? '(somente anexo)' : '');
    li.appendChild(text);

    // Anexos
    if (Array.isArray(it.attachments) && it.attachments.length > 0) {
      const att = document.createElement('span');
      att.className = 'msg-text';
      att.style.cssText = 'font-size:9.5px;color:var(--text3);';
      att.textContent = '\uD83D\uDCCE ' + it.attachments.join(', ');
      li.appendChild(att);
    }

    // Timestamp — linha separada
    const t = document.createElement('time');
    t.textContent = formatChatTime(it.at);
    if (it.ok === false && it.error) {
      const errNote = document.createElement('span');
      errNote.style.cssText = 'color:var(--red);font-size:10px;display:block;margin-top:2px;';
      errNote.textContent = it.error;
      li.appendChild(errNote);
    }
    li.appendChild(t);
    ul.appendChild(li);
  }
  ul.scrollTop = ul.scrollHeight;
}

function formatChatTime(ms) {
  if (!ms) return '';
  const d = new Date(ms);
  if (Number.isNaN(d.getTime())) return '';
  const pad = (n) => String(n).padStart(2, '0');
  return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

async function appendChatHistory(entry) {
  if (!_activeProjectId) return;
  const all = { ...(settings?.tryToFixHistory || {}) };
  const list = (all[_activeProjectId] || []).slice(-29);
  list.push(entry);
  all[_activeProjectId] = list;
  const updated = await sendMessage({ type: 'SET_SETTINGS', updates: { tryToFixHistory: all } });
  if (updated) settings = updated;
  renderChatHistory();
}

function setChatStatus(text, kind) {
  const el = $('chat-status');
  if (!el) return;
  if (!text) {
    el.hidden = true;
    el.removeAttribute('data-kind');
    _cancelAutoClear('chat-status');
    return;
  }
  el.hidden = false;
  el.textContent = text;
  if (kind) el.dataset.kind = kind;
  if (kind === 'error' || kind === 'ok') _scheduleAutoClear('chat-status', () => setChatStatus(''));
  else el.removeAttribute('data-kind');
}

async function clearChatHistory() {
  if (!_activeProjectId) return;
  const confirmed = await showConfirm({
    title: 'Limpar histórico?',
    msg: 'As mensagens enviadas neste projeto serão apagadas. Esta ação não pode ser desfeita.',
    confirmText: 'Limpar tudo',
    cancelText: 'Cancelar',
  });
  if (!confirmed) return;

  const res = await sendMessage({ type: 'CLEAR_CHAT_HISTORY', projectId: _activeProjectId });
  if (res?.ok) {
    // Recarrega settings pra refletir a remoção (bypass do deepMerge no background)
    settings = await sendMessage({ type: 'GET_SETTINGS' });
    renderChatHistory();
    setChatStatus('Histórico limpo.', 'ok');
    setTimeout(() => setChatStatus(''), 2500);
  } else {
    setChatStatus('Falha ao limpar.', 'error');
  }
}

/* ============================================================
   Auto-resize do textarea — cresce conforme digita até max
   ============================================================ */
function autoResizeTextarea(el) {
  if (!el) return;
  const MAX = 280;
  el.style.height = 'auto';
  const next = Math.min(el.scrollHeight, MAX);
  el.style.height = next + 'px';
}

/* ============================================================
   Melhorar Prompt — usa Pollinations.ai (grátis, sem API key)
   ============================================================ */
async function improvePrompt() {
  if (!requireValidLicense(setChatStatus)) return;
  const btn = $('chat-improve');
  const input = $('chat-input');
  const iconEl = btn?.querySelector('.chat-improve-icon');
  const spinnerEl = btn?.querySelector('.chat-improve-spinner');
  const text = (input?.value || '').trim();

  if (!text) {
    setChatStatus('Digite algo primeiro para melhorar.', 'error');
    return;
  }
  if (!btn || btn.dataset.busy === 'true') return;

  btn.dataset.busy = 'true';
  if (iconEl) iconEl.hidden = true;
  if (spinnerEl) spinnerEl.hidden = false;
  setChatStatus('✨ Melhorando prompt…');

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout

  try {
    const res = await fetch('https://text.pollinations.ai/openai', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      signal: controller.signal,
      body: JSON.stringify({
        model: 'openai',
        messages: [
          {
            role: 'system',
            content:
              'Você é um especialista em escrever prompts para IAs de programação (como a Lovable). ' +
              'Reescreva o prompt do usuário deixando-o claro, específico, técnico e bem estruturado, ' +
              'mantendo a intenção original. Use português brasileiro. ' +
              'Adicione contexto e detalhes que ajudem a IA a entender o que precisa ser feito. ' +
              'Responda APENAS com o prompt melhorado, sem nenhum comentário, prefácio ou explicação. ' +
              'Não use markdown. Apenas o texto bruto do novo prompt.',
          },
          { role: 'user', content: text },
        ],
        temperature: 0.7,
      }),
    });

    if (!res.ok) {
      const detail = await res.text().catch(() => '');
      throw new Error(`HTTP ${res.status} ${detail.slice(0, 80)}`);
    }
    const data = await res.json();
    const improved = (data?.choices?.[0]?.message?.content || '').trim();

    if (improved) {
      input.value = improved;
      autoResizeTextarea(input); // ajusta altura ao novo conteúdo
      input.focus();
      // Posiciona cursor no fim
      input.setSelectionRange(input.value.length, input.value.length);
      setChatStatus('✓ Prompt melhorado.', 'ok');
      setTimeout(() => setChatStatus(''), 3000);
    } else {
      setChatStatus('A IA não retornou texto. Tente de novo.', 'error');
    }
  } catch (e) {
    console.error('[PULSE popup] improve prompt failed:', e);
    if (e?.name === 'AbortError') {
      setChatStatus('Tempo esgotado. Tente novamente.', 'error');
    } else {
      setChatStatus('Falha ao melhorar: ' + (e?.message || e), 'error');
    }
  } finally {
    clearTimeout(timeoutId);
    delete btn.dataset.busy;
    if (iconEl) iconEl.hidden = false;
    if (spinnerEl) spinnerEl.hidden = true;
  }
}

/* ----------------- Modal de confirmação reutilizável ----------------- */
function showConfirm(opts = {}) {
  return new Promise((resolve) => {
    const modal = $('confirm-modal');
    const titleEl = $('confirm-modal-title');
    const msgEl = $('confirm-modal-msg');
    const okBtn = $('confirm-modal-confirm');
    const cancelBtn = $('confirm-modal-cancel');
    if (!modal || !okBtn || !cancelBtn) {
      resolve(false);
      return;
    }
    if (titleEl) titleEl.textContent = opts.title || 'Confirmar?';
    if (msgEl) msgEl.textContent = opts.msg || '';
    okBtn.textContent = opts.confirmText || 'Confirmar';
    cancelBtn.textContent = opts.cancelText || 'Cancelar';

    modal.hidden = false;

    const cleanup = () => {
      okBtn.removeEventListener('click', onOk);
      cancelBtn.removeEventListener('click', onCancel);
      modal.removeEventListener('click', onBackdrop);
      document.removeEventListener('keydown', onKey, true);
      modal.hidden = true;
    };
    const close = (val) => { cleanup(); resolve(val); };
    const onOk = () => close(true);
    const onCancel = () => close(false);
    const onBackdrop = (e) => {
      if (e.target === modal || e.target.classList?.contains('confirm-modal-backdrop')) onCancel();
    };
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); onCancel(); }
      else if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); onOk(); }
    };

    okBtn.addEventListener('click', onOk);
    cancelBtn.addEventListener('click', onCancel);
    modal.addEventListener('click', onBackdrop);
    document.addEventListener('keydown', onKey, true);
    setTimeout(() => cancelBtn.focus(), 0);
  });
}

// Flag de sessão — aviso de Modo Plano só na 1ª vez por abertura da extensão
let _planModeWarned = false;

// Auto-recuperacao: se o content script estiver offline (acontece depois de
// recarregar/atualizar a extensao), recarrega a aba do Lovable sozinho e espera
// ficar pronta — assim o usuario nao precisa dar F5 manual.
async function _ensureContentAlive(setStatusFn) {
  async function _ping(id) {
    try {
      const pong = await Promise.race([
        chrome.tabs.sendMessage(id, { type: 'PING' }),
        new Promise((_, rj) => setTimeout(() => rj(new Error('t')), 1200)),
      ]);
      return !!pong?.ok;
    } catch (_) {
      return false;
    }
  }
  let tab = await getLovableTab();
  if (!tab?.id) return null;
  if (await _ping(tab.id)) return tab;
  // offline -> recarrega a aba (auto-F5)
  if (typeof setStatusFn === 'function') {
    try { setStatusFn('🔄 Reconectando à página do Lovable…'); } catch (_) {}
  }
  await new Promise((resolve) => {
    let done = false;
    const fin = () => {
      if (!done) {
        done = true;
        try { chrome.tabs.onUpdated.removeListener(lis); } catch (_) {}
        resolve();
      }
    };
    const lis = (tid, info) => {
      if (tid === tab.id && info.status === 'complete') fin();
    };
    chrome.tabs.onUpdated.addListener(lis);
    try { chrome.tabs.reload(tab.id); } catch (_) { fin(); }
    setTimeout(fin, 9000);
  });
  // espera o content script subir e responder ao PING (ate ~7s)
  const t0 = Date.now();
  while (Date.now() - t0 < 7000) {
    await new Promise((r) => setTimeout(r, 500));
    tab = await getLovableTab();
    if (tab?.id && (await _ping(tab.id))) return tab;
  }
  return tab;
}

async function sendTryToFix() {
  if (!requireValidLicense(setChatStatus)) return;
  const btn = $('chat-send');
  const input = $('chat-input');
  const iconEl = btn.querySelector('.chat-send-icon');
  const spinnerEl = btn.querySelector('.chat-send-spinner');
  const text = (input?.value || '').trim();

  if (!text && _pendingAttachments.length === 0) {
    setChatStatus('Digite um problema ou anexe uma mídia.', 'error');
    return;
  }
  if (!_activeProjectId) {
    setChatStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }

  // Aviso de 1ª vez do Modo Plano (só nessa sessão da extensão)
  if (getCurrentChatMode() && !_planModeWarned) {
    const ok = await showConfirm({
      title: 'Modo Plano consome créditos',
      msg: 'O Modo Plano faz a IA gerar um plano detalhado antes de executar e consome ~1 crédito da sua Lovable. Quer continuar?',
      confirmText: 'Continuar',
      cancelText: 'Cancelar',
    });
    if (!ok) return;
    _planModeWarned = true;
  }

  const hasAttachments = _pendingAttachments.length > 0;
  const setBusy = (busy) => {
    btn.disabled = busy;
    input.disabled = busy;
    if (iconEl) iconEl.hidden = busy;
    if (spinnerEl) spinnerEl.hidden = !busy;
  };
  setBusy(true);

  // Se o content script estiver offline (apos recarregar a extensao), recarrega
  // a aba do Lovable sozinho e espera ficar pronta — sem precisar de F5 manual.
  await _ensureContentAlive(setChatStatus);

  const token = await waitForLovableToken(setChatStatus);
  if (!token) {
    setBusy(false);
    setChatStatus('Recarregue a página do Lovable (F5) e envie de novo.', 'error');
    return;
  }
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    setBusy(false);
    await showDetailedError(setChatStatus, 'E-NO-TAB', 'Aba ativa não é projeto Lovable.');
    return;
  }

  setChatStatus(hasAttachments ? 'Subindo anexos…' : 'Enviando mensagem…');

  const attachmentSummary = _pendingAttachments.map((a) => a.name);
  let attachmentsTransferable = [];
  try {
    attachmentsTransferable = await attachmentsToTransferable();
  } catch (e) {
    setChatStatus('Não consegui ler os anexos: ' + (e?.message || e), 'error');
    setBusy(false);
    return;
  }

  try {
    // Re-lê settings pra pegar session-id/git-sha mais recentes — a Lovable
    // exige esses headers em endpoints sensíveis (ex.: upload de imagem no chat).
    const freshChat = await sendMessage({ type: 'GET_SETTINGS' }).catch(() => null);
    if (freshChat) settings = freshChat;
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'SEND_TRY_TO_FIX',
      projectId: _activeProjectId,
      token,
      text,
      attachments: attachmentsTransferable,
      chatMode: getCurrentChatMode(),
      sessionId: settings?.lovableSessionId || '',
      gitSha: settings?.lovableClientGitSha || '',
    });
    if (res?.ok) {
      await appendChatHistory({
        text,
        at: Date.now(),
        ok: true,
        error_id: res.error_id || null,
        attachments: attachmentSummary,
      });
      setChatStatus('✓ Mensagem enviada.', 'ok');
      input.value = '';
      input.style.height = '';
      clearAttachments();
      setTimeout(() => setChatStatus(''), 5000);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      await appendChatHistory({ text, at: Date.now(), ok: false, error: errMsg, attachments: attachmentSummary });
      if (res?.status === 401 || res?.status === 403 || /401|403/.test(errMsg)) {
        await showDetailedError(setChatStatus, 'E-AUTH', 'Token rejeitado pela Lovable (401/403). F5 na aba pra renovar.');
      } else {
        setChatStatus('[E-HTTP] ' + errMsg, 'error');
      }
    }
  } catch (e) {
    console.error('[PULSE popup] try-to-fix dispatch failed:', e);
    setChatStatus(formatTabError(e), 'error');
  } finally {
    setBusy(false);
  }
}

// Mostra/esconde o botão "Aprovar plano pelo chat".
function setApprovePlanVisible(show) {
  const btn = $('chat-approve-plan');
  if (btn) btn.hidden = !show;
}

// Pergunta ao content script se há um plano pendente na página e ajusta o botão.
async function refreshPlanButton() {
  try {
    const tab = await getLovableTab();
    if (!tab?.id) { setApprovePlanVisible(false); return; }
    const res = await chrome.tabs.sendMessage(tab.id, { type: 'GET_PLAN_STATE' }).catch(() => null);
    setApprovePlanVisible(!!(res && res.pending));
  } catch (_) {
    setApprovePlanVisible(false);
  }
}

// Aprova o plano pendente enviando uma mensagem fixa no chat (modo Construir),
// em vez de usar o botão "Aprovar" nativo da Lovable.
async function approvePlanViaChat() {
  if (!requireValidLicense(setChatStatus)) return;
  if (!_activeProjectId) {
    setChatStatus('Abra um projeto em lovable.dev/projects/<id> primeiro.', 'error');
    return;
  }

  const btn = $('chat-approve-plan');
  const APPROVE_MSG = 'Aprove este plano e todas as ações que contém nele';

  if (btn) btn.disabled = true;
  setChatStatus('Aprovando plano…');

  const token = await waitForLovableToken(setChatStatus);
  if (!token) {
    if (btn) btn.disabled = false;
    setChatStatus('Recarregue a página do Lovable.', 'error');
    return;
  }
  const tab = await getLovableTab();
  if (!tab?.id || !/https:\/\/lovable\.dev\/projects\//i.test(tab.url || '')) {
    if (btn) btn.disabled = false;
    await showDetailedError(setChatStatus, 'E-NO-TAB', 'Aba ativa não é projeto Lovable.');
    return;
  }

  try {
    const fresh = await sendMessage({ type: 'GET_SETTINGS' }).catch(() => null);
    if (fresh) settings = fresh;
    const res = await chrome.tabs.sendMessage(tab.id, {
      type: 'SEND_TRY_TO_FIX',
      projectId: _activeProjectId,
      token,
      text: APPROVE_MSG,
      attachments: [],
      chatMode: false,   // modo Construir — envia como "Try to Fix" (fix error)
      sessionId: settings?.lovableSessionId || '',
      gitSha: settings?.lovableClientGitSha || '',
    });
    if (res?.ok) {
      await appendChatHistory({ text: APPROVE_MSG, at: Date.now(), ok: true, attachments: [] });
      setChatStatus('✓ Plano aprovado pelo chat.', 'ok');
      setTimeout(() => setChatStatus(''), 5000);
    } else {
      const errMsg = String(res?.error || 'falha desconhecida');
      if (res?.status === 401 || res?.status === 403 || /401|403/.test(errMsg)) {
        await showDetailedError(setChatStatus, 'E-AUTH', 'Token rejeitado pela Lovable (401/403). F5 na aba pra renovar.');
      } else {
        setChatStatus('[E-HTTP] ' + errMsg, 'error');
      }
    }
  } catch (e) {
    console.error('[PULSE popup] approve-plan dispatch failed:', e);
    setChatStatus(formatTabError(e), 'error');
  } finally {
    if (btn) btn.disabled = false;
  }
}

/* ============================================================
   Anexos do chat (imagem + áudio)
   ============================================================ */
const MAX_ATTACHMENT_SIZE = 12 * 1024 * 1024; // 12 MB por arquivo
const _pendingAttachments = []; // [{ id, name, mime, blob, previewUrl }]

let _mediaRecorder = null;
let _recordChunks = [];
let _recordStartedAt = 0;
let _recordTimerId = null;
let _recordStream = null;

function bindChatAttachments() {
  const attachBtn = $('chat-attach-image');
  const fileInput = $('chat-image-input');
  const recordBtn = $('chat-record-audio');
  const textarea = $('chat-input');

  attachBtn?.addEventListener('click', () => fileInput?.click());
  fileInput?.addEventListener('change', (e) => {
    const files = Array.from(e.target.files || []);
    for (const f of files) addAttachmentFromFile(f);
    fileInput.value = '';
  });

  // Colar imagens com Ctrl+V
  textarea?.addEventListener('paste', (e) => {
    const items = e.clipboardData?.items || [];
    let added = 0;
    for (const it of items) {
      if (it.kind === 'file') {
        const f = it.getAsFile();
        if (f && f.type.startsWith('image/')) {
          addAttachmentFromFile(f);
          added++;
        }
      }
    }
    if (added > 0) e.preventDefault();
  });

  recordBtn?.addEventListener('click', toggleRecording);
}

function addAttachmentFromFile(file) {
  if (!requireValidLicense(setChatStatus)) return;
  if (file.size > MAX_ATTACHMENT_SIZE) {
    setChatStatus(`"${file.name}" passa de ${Math.round(MAX_ATTACHMENT_SIZE / 1024 / 1024)} MB.`, 'error');
    return;
  }
  const id = Math.random().toString(36).slice(2, 10);
  const previewUrl = file.type.startsWith('image/') ? URL.createObjectURL(file) : null;
  _pendingAttachments.push({
    id,
    name: file.name || (file.type.startsWith('image/') ? 'imagem.png' : 'arquivo'),
    mime: file.type || 'application/octet-stream',
    blob: file,
    previewUrl,
  });
  renderAttachments();
}

function removeAttachment(id) {
  const idx = _pendingAttachments.findIndex((a) => a.id === id);
  if (idx === -1) return;
  const [att] = _pendingAttachments.splice(idx, 1);
  if (att?.previewUrl) URL.revokeObjectURL(att.previewUrl);
  renderAttachments();
}

function clearAttachments() {
  for (const att of _pendingAttachments) {
    if (att.previewUrl) URL.revokeObjectURL(att.previewUrl);
  }
  _pendingAttachments.length = 0;
  renderAttachments();
}

function renderAttachments() {
  const ul = $('chat-attachments');
  if (!ul) return;
  if (_pendingAttachments.length === 0) {
    ul.hidden = true;
    ul.innerHTML = '';
    return;
  }
  ul.hidden = false;
  ul.innerHTML = '';
  for (const att of _pendingAttachments) {
    const li = document.createElement('li');

    if (att.previewUrl) {
      const img = document.createElement('img');
      img.className = 'att-thumb';
      img.src = att.previewUrl;
      img.alt = att.name;
      li.appendChild(img);
    } else {
      const ic = document.createElement('span');
      ic.className = 'att-thumb-icon';
      ic.innerHTML = att.mime.startsWith('audio/')
        ? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/></svg>'
        : '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
      li.appendChild(ic);
    }

    const name = document.createElement('span');
    name.className = 'att-name';
    name.textContent = att.name;
    li.appendChild(name);

    const size = document.createElement('span');
    size.className = 'att-size';
    size.textContent = formatBytes(att.blob.size);
    li.appendChild(size);

    const rm = document.createElement('button');
    rm.type = 'button';
    rm.className = 'att-remove';
    rm.title = 'Remover';
    rm.textContent = '×';
    rm.addEventListener('click', () => removeAttachment(att.id));
    li.appendChild(rm);

    ul.appendChild(li);
  }
}

function formatBytes(n) {
  if (!n && n !== 0) return '';
  if (n < 1024) return n + ' B';
  if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
  return (n / 1024 / 1024).toFixed(1) + ' MB';
}

/* ----------------- Gravação de áudio ----------------- */
async function toggleRecording() {
  if (_mediaRecorder && _mediaRecorder.state === 'recording') {
    stopRecording();
  } else {
    await startRecording();
  }
}

async function startRecording() {
  if (!requireValidLicense(setChatStatus)) return;
  const btn = $('chat-record-audio');
  const indicator = $('chat-rec-indicator');

  // Pré-check via Permissions API — se já está negado, mostra a UI de erro
  // sem nem tentar o getUserMedia (que falharia silenciosamente).
  if (navigator.permissions?.query) {
    try {
      const status = await navigator.permissions.query({ name: 'microphone' });
      if (status.state === 'denied') {
        showMicPermissionError();
        return;
      }
    } catch (_) { /* alguns browsers não suportam — segue tentando */ }
  }

  try {
    _recordStream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const mime = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
      ? 'audio/webm;codecs=opus'
      : (MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : '');
    _mediaRecorder = new MediaRecorder(_recordStream, mime ? { mimeType: mime } : undefined);
    _recordChunks = [];
    _mediaRecorder.ondataavailable = (e) => {
      if (e.data && e.data.size > 0) _recordChunks.push(e.data);
    };
    _mediaRecorder.onstop = () => finalizeRecording();
    _mediaRecorder.start();
    _recordStartedAt = Date.now();
    btn.dataset.recording = 'true';
    btn.title = 'Parar gravação';
    if (indicator) indicator.hidden = false;
    _recordTimerId = setInterval(updateRecordTimer, 250);
    setChatStatus('');
  } catch (e) {
    console.error('[PULSE popup] mic getUserMedia failed:', e?.name, e?.message);
    const name = e?.name || '';
    if (name === 'NotAllowedError' || name === 'PermissionDeniedError' || name === 'SecurityError') {
      showMicPermissionError();
    } else if (name === 'NotFoundError' || name === 'DevicesNotFoundError') {
      setChatStatus('Nenhum microfone encontrado no dispositivo.', 'error');
    } else if (name === 'NotReadableError' || name === 'TrackStartError') {
      setChatStatus('Microfone em uso por outro aplicativo.', 'error');
    } else {
      setChatStatus('Erro de microfone: ' + (e?.message || name || 'desconhecido'), 'error');
    }
    cleanupRecording();
  }
}

function showMicPermissionError() {
  const el = $('chat-status');
  if (!el) return;
  el.hidden = false;
  el.dataset.kind = 'error';
  // Limpa filhos antes de remontar (em vez de innerHTML por segurança)
  while (el.firstChild) el.removeChild(el.firstChild);

  const msg = document.createElement('span');
  msg.textContent = 'Microfone bloqueado. ';
  el.appendChild(msg);

  const link = document.createElement('a');
  link.href = '#';
  link.textContent = 'Abrir configurações';
  link.style.cssText = 'color: var(--purple); text-decoration: underline; cursor: pointer; font-weight: 600;';
  link.addEventListener('click', (ev) => {
    ev.preventDefault();
    const extId = chrome.runtime.id;
    // chrome:// URLs precisam ser abertas via chrome.tabs.create
    chrome.tabs.create({
      url: `chrome://settings/content/siteDetails?site=chrome-extension://${extId}`,
    });
  });
  el.appendChild(link);

  const hint = document.createElement('span');
  hint.textContent = ' e marque "Permitir" em Microfone.';
  el.appendChild(hint);
}

function updateRecordTimer() {
  const el = $('chat-rec-time');
  if (!el) return;
  const sec = Math.floor((Date.now() - _recordStartedAt) / 1000);
  const m = String(Math.floor(sec / 60)).padStart(2, '0');
  const s = String(sec % 60).padStart(2, '0');
  el.textContent = `${m}:${s}`;
  // limite de 3 minutos por gravação
  if (sec >= 180) stopRecording();
}

function stopRecording() {
  if (_mediaRecorder && _mediaRecorder.state !== 'inactive') {
    try { _mediaRecorder.stop(); } catch (_) {}
  }
}

function finalizeRecording() {
  const blob = new Blob(_recordChunks, { type: _mediaRecorder?.mimeType || 'audio/webm' });
  const sec = Math.max(1, Math.round((Date.now() - _recordStartedAt) / 1000));
  const ext = (blob.type.includes('webm') ? 'webm' : (blob.type.split('/')[1] || 'webm')).split(';')[0];
  const name = `audio-${sec}s.${ext}`;
  // empacota como File-like
  const file = new File([blob], name, { type: blob.type });
  if (file.size <= MAX_ATTACHMENT_SIZE) {
    addAttachmentFromFile(file);
  } else {
    setChatStatus(`Áudio passou de ${Math.round(MAX_ATTACHMENT_SIZE / 1024 / 1024)} MB.`, 'error');
  }
  cleanupRecording();
}

function cleanupRecording() {
  if (_recordTimerId) {
    clearInterval(_recordTimerId);
    _recordTimerId = null;
  }
  if (_recordStream) {
    _recordStream.getTracks().forEach((t) => t.stop());
    _recordStream = null;
  }
  _mediaRecorder = null;
  _recordChunks = [];
  const btn = $('chat-record-audio');
  if (btn) {
    btn.dataset.recording = 'false';
    btn.title = 'Gravar áudio';
  }
  const indicator = $('chat-rec-indicator');
  if (indicator) {
    indicator.hidden = true;
    const tEl = $('chat-rec-time');
    if (tEl) tEl.textContent = '00:00';
  }
}

function arrayBufferToBase64(buf) {
  const bytes = new Uint8Array(buf);
  let bin = '';
  const CHUNK = 0x8000;
  for (let i = 0; i < bytes.length; i += CHUNK) {
    bin += String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK));
  }
  return btoa(bin);
}

async function attachmentsToTransferable() {
  const out = [];
  for (const att of _pendingAttachments) {
    const buf = await att.blob.arrayBuffer();
    // base64 porque ArrayBuffer NÃO sobrevive ao chrome.tabs.sendMessage
    // (vira {} → o content geraria um Blob de "[object Object]" = 15 bytes).
    out.push({ name: att.name, mime: att.mime, dataB64: arrayBufferToBase64(buf) });
  }
  return out;
}

/* ============================================================
   Tabs (Início / Projeto / Chat)
   ============================================================ */
function bindTabs() {
  // Suporta .nav-item (sidebar) e .tab (legado)
  document.querySelectorAll('.nav-item[data-tab], .tab[data-tab]').forEach((btn) => {
    btn.addEventListener('click', () => activateTab(btn.dataset.tab));
  });
  // Sidebar collapse toggle
  const sidebarToggle = document.getElementById('sidebar-toggle');
  const sidebar = document.getElementById('sidebar');
  if (sidebarToggle && sidebar) {
    sidebarToggle.addEventListener('click', () => sidebar.classList.toggle('collapsed'));
  }
  // No novo layout flat "home" está oculto — sempre começa em "chat".
  activateTab('chat', { persist: false });
}

function activateTab(name, opts = {}) {
  const { persist = true } = opts;
  document.querySelectorAll('.nav-item[data-tab], .tab[data-tab]').forEach((btn) => {
    const isActive = btn.dataset.tab === name;
    btn.classList.toggle('active', isActive);
    btn.dataset.active = isActive ? 'true' : 'false';
    btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
  });
  document.querySelectorAll('.tab-panel[data-panel]').forEach((panel) => {
    panel.hidden = panel.dataset.panel !== name;
  });
  // Atualiza título do content-header
  const titleEl = document.getElementById('content-title');
  if (titleEl) titleEl.textContent = _TAB_TITLES[name] || name;
  // Limpa status pendentes ao trocar de aba
  try {
    setDownloadStatus('');
    setHideBadgeStatus('');
    setPublishStatus('');
    setCreateStatus('');
    setSecurityStatus('');
    setChatStatus('');
  } catch (_) {}
  if (persist) {
    sendMessage({ type: 'SET_SETTINGS', updates: { activeTab: name } });
  }
}

function sendMessage(msg) {
  return new Promise((resolve) => chrome.runtime.sendMessage(msg, resolve));
}
