// Integrações — Meta Ads + Bot de Notificações

const WEBHOOK_URL   = 'https://horos.sale/api/meta-webhook';
const VERIFY_TOKEN  = 'horos_meta_wh_v1';

// ── Modal OAuth Facebook (popup manual — sem FB SDK) ─────────────────────────
function MetaConnectModal({ onClose, onConnected }) {
  const [phase, setPhase]           = React.useState('ready'); // ready | loading | selecting | error
  const [pages, setPages]           = React.useState([]);
  const [error, setError]           = React.useState('');
  const [connecting, setConnecting] = React.useState(null);

  const handleFBLogin = async () => {
    setPhase('loading');
    try {
      const cfgRes = await fetch('/api/meta-oauth?config');
      const { app_id } = await cfgRes.json();
      if (!app_id) throw new Error('META_APP_ID não configurado no servidor.');

      const callbackUrl = encodeURIComponent('https://horos.sale/meta-oauth-callback.html');
      const scope = encodeURIComponent('pages_show_list,pages_manage_metadata,leads_retrieval');
      const oauthUrl = `https://www.facebook.com/v22.0/dialog/oauth?client_id=${app_id}&redirect_uri=${callbackUrl}&scope=${scope}&response_type=code&display=popup`;

      const popup = window.open(oauthUrl, 'fb-oauth', 'width=600,height=650,scrollbars=yes,resizable=yes');
      if (!popup) throw new Error('Popup bloqueado pelo navegador. Permita popups para horos.sale nas configurações do navegador e tente novamente.');

      const oauthResult = await new Promise((resolve, reject) => {
        const handler = (event) => {
          if (event.origin !== 'https://horos.sale') return;
          window.removeEventListener('message', handler);
          clearInterval(closedCheck);
          if (event.data && (event.data.type === 'fb-oauth-token' || event.data.type === 'fb-oauth-code')) resolve(event.data);
          else reject(new Error((event.data && event.data.error) || 'Erro na autorização do Facebook.'));
        };
        window.addEventListener('message', handler);
        const closedCheck = setInterval(() => {
          if (popup.closed) {
            clearInterval(closedCheck);
            window.removeEventListener('message', handler);
            reject(new Error('Janela fechada antes de concluir. Tente novamente.'));
          }
        }, 800);
      });

      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      if (!session) throw new Error('Sessão expirada. Recarregue a página e tente novamente.');
      const oauthBody = oauthResult.type === 'fb-oauth-code'
        ? { code: oauthResult.code }
        : { user_token: oauthResult.access_token };
      const oRes = await fetch('/api/meta-oauth', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` },
        body: JSON.stringify(oauthBody),
      });
      const oData = await oRes.json();
      if (!oRes.ok) throw new Error('Erro ao processar autorização com o Facebook. Tente novamente.');
      const pageList = oData.pages || [];
      if (pageList.length === 0) throw new Error('Nenhuma Página encontrada. Verifique se você administra páginas do Facebook com esta conta.');
      setPages(pageList);
      setPhase('selecting');
    } catch (e) {
      setError(e.message);
      setPhase('error');
    }
  };

  const connectPage = async (page) => {
    setConnecting(page.id);
    try {
      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      if (!session) throw new Error('Sessão expirada. Recarregue a página e tente novamente.');
      const res = await fetch('/api/meta-connect', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` },
        body: JSON.stringify({ page_id: page.id, page_access_token: page.access_token }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error('Erro ao conectar a página. Tente novamente.');
      onConnected({ id: 'opt-' + page.id, page_id: page.id, page_name: data.page_name || page.name, leads_count: 0, active: true });
    } catch (e) {
      setError(e.message);
      setPhase('error');
    } finally {
      setConnecting(null);
    }
  };

  const S = {
    overlay:  { position:'fixed', inset:0, zIndex:2000, display:'flex', alignItems:'center', justifyContent:'center' },
    backdrop: { position:'absolute', inset:0, background:'rgba(0,0,0,0.55)', backdropFilter:'blur(6px)' },
    box:      { position:'relative', background:'var(--card)', borderRadius:20, width:440, maxWidth:'94vw', padding:28, boxShadow:'0 24px 60px rgba(0,0,0,0.3)', border:'1px solid var(--line-strong)' },
  };

  return (
    <div style={S.overlay}>
      <div style={S.backdrop} onClick={phase !== 'loading' ? onClose : undefined} />
      <div style={S.box}>

        {/* Header */}
        <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:24 }}>
          <div style={{ width:38, height:38, borderRadius:10, background:'linear-gradient(135deg,#1877f2,#42a5f5)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="#fff"><path d="M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.41c0-3.025 1.791-4.697 4.533-4.697 1.312 0 2.686.236 2.686.236v2.97H15.83c-1.491 0-1.956.93-1.956 1.886v2.276h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z"/></svg>
          </div>
          <div>
            <div style={{ fontSize:16, fontWeight:700, color:'var(--fg)', lineHeight:1.2 }}>Conectar Meta Ads</div>
            <div style={{ fontSize:11, color:'var(--fg-3)', marginTop:2 }}>Leads do Facebook e Instagram entram direto no CRM</div>
          </div>
          {phase !== 'loading' && (
            <button onClick={onClose} style={{ marginLeft:'auto', background:'var(--bg-1)', border:'1px solid var(--line-strong)', cursor:'pointer', color:'var(--fg-2)', width:30, height:30, borderRadius:8, display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
              <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="m2 2 10 10M12 2 2 12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"/></svg>
            </button>
          )}
        </div>

        {/* Carregando */}
        {phase === 'loading' && (
          <div style={{ textAlign:'center', padding:'32px 0' }}>
            <svg style={{ animation:'spin .9s linear infinite' }} width="32" height="32" viewBox="0 0 24 24" fill="none">
              <circle cx="12" cy="12" r="10" stroke="rgba(24,119,242,0.2)" strokeWidth="3"/>
              <path d="M12 2a10 10 0 0 1 10 10" stroke="#1877f2" strokeWidth="3" strokeLinecap="round"/>
            </svg>
            <div style={{ marginTop:14, fontSize:13, color:'var(--fg-2)' }}>Abrindo Facebook…</div>
          </div>
        )}

        {/* Pronto — botão principal */}
        {phase === 'ready' && (
          <div style={{ textAlign:'center' }}>
            <div style={{ fontSize:13, color:'var(--fg-2)', marginBottom:24, lineHeight:1.8 }}>
              Clique no botão abaixo, autorize o Horos no Facebook e selecione qual página conectar.
              <br/>Seus leads entrarão automaticamente no <b style={{ color:'var(--fg)' }}>Inbox do CRM</b>.
            </div>
            <button onClick={handleFBLogin}
              style={{ display:'inline-flex', alignItems:'center', gap:10, padding:'13px 24px', background:'#1877f2', color:'#fff', border:'none', borderRadius:10, fontSize:14, fontWeight:700, cursor:'pointer', width:'100%', justifyContent:'center', boxShadow:'0 2px 8px rgba(24,119,242,0.3)' }}>
              <svg width="18" height="18" viewBox="0 0 24 24" fill="#fff"><path d="M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.41c0-3.025 1.791-4.697 4.533-4.697 1.312 0 2.686.236 2.686.236v2.97H15.83c-1.491 0-1.956.93-1.956 1.886v2.276h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z"/></svg>
              Continuar com o Facebook
            </button>
            <div style={{ marginTop:12, fontSize:11, color:'var(--fg-4)' }}>
              Permissões solicitadas: gerenciar páginas e receber leads de anúncios
            </div>
          </div>
        )}

        {/* Seleção de página */}
        {phase === 'selecting' && (
          <div>
            <div style={{ fontSize:13, color:'var(--fg-2)', marginBottom:14 }}>
              {pages.length === 1 ? 'Página encontrada:' : `${pages.length} páginas encontradas — selecione qual conectar:`}
            </div>
            <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
              {pages.map(page => (
                <div key={page.id} style={{ display:'flex', alignItems:'center', gap:12, padding:'12px 14px', border:'1px solid var(--line-strong)', borderRadius:10, background:'var(--bg-1)' }}>
                  <div style={{ width:36, height:36, borderRadius:10, background:'linear-gradient(135deg,#1877f2,#42a5f5)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, fontSize:14, color:'#fff', fontWeight:700 }}>
                    {(page.name || 'P')[0].toUpperCase()}
                  </div>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:13, fontWeight:600, color:'var(--fg)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{page.name}</div>
                    <div style={{ fontSize:11, color:'var(--fg-4)' }}>{page.category}</div>
                  </div>
                  <button onClick={() => connectPage(page)} disabled={!!connecting}
                    style={{ padding:'7px 16px', background: connecting === page.id ? '#4f52c8' : '#1F93FF', color:'#fff', border:'none', borderRadius:8, fontSize:12, fontWeight:600, cursor: connecting ? 'not-allowed' : 'pointer', display:'flex', alignItems:'center', gap:6, flexShrink:0 }}>
                    {connecting === page.id
                      ? <><svg style={{ animation:'spin .9s linear infinite' }} width="11" height="11" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="rgba(255,255,255,0.3)" strokeWidth="3"/><path d="M12 2a10 10 0 0 1 10 10" stroke="#fff" strokeWidth="3" strokeLinecap="round"/></svg>Conectando…</>
                      : 'Conectar'}
                  </button>
                </div>
              ))}
            </div>
          </div>
        )}

        {/* Erro */}
        {phase === 'error' && (
          <div style={{ textAlign:'center' }}>
            <div style={{ fontSize:13, color:'var(--danger)', marginBottom:20, lineHeight:1.6, background:'color-mix(in srgb, #ff5a4e 10%, var(--card))', border:'1px solid color-mix(in srgb, #ff5a4e 35%, var(--card))', borderRadius:8, padding:'12px 16px', textAlign:'left' }}>
              {error}
            </div>
            <button onClick={onClose} style={{ padding:'9px 20px', background:'var(--bg-1)', border:'1px solid var(--line-strong)', borderRadius:9, fontSize:13, fontWeight:500, color:'var(--fg-2)', cursor:'pointer' }}>
              Fechar
            </button>
          </div>
        )}

      </div>
    </div>
  );
}

// ── Google OAuth helper ───────────────────────────────────────────────────────
async function openGoogleOAuth() {
  const cfgRes = await fetch('/api/google-calendar?oauth&config');
  const { client_id } = await cfgRes.json();
  if (!client_id) throw new Error('GOOGLE_CLIENT_ID não configurado.');
  const params = new URLSearchParams({
    client_id,
    redirect_uri:  'https://horos.sale/google-oauth-callback.html',
    response_type: 'code',
    scope:         'https://www.googleapis.com/auth/calendar.events email profile openid',
    access_type:   'offline',
    prompt:        'consent',
  });
  return window.open(
    `https://accounts.google.com/o/oauth2/v2/auth?${params}`,
    'google-oauth',
    'width=520,height=640,top=100,left=200'
  );
}

// ── Catálogo (layout de marketplace) ──────────────────────────────────────────
const FB_PATH = 'M24 12.073C24 5.405 18.627 0 12 0S0 5.405 0 12.073C0 18.1 4.388 23.094 10.125 24v-8.437H7.078v-3.49h3.047V9.41c0-3.025 1.791-4.697 4.533-4.697 1.312 0 2.686.236 2.686.236v2.97H15.83c-1.491 0-1.956.93-1.956 1.886v2.276h3.328l-.532 3.49h-2.796V24C19.612 23.094 24 18.1 24 12.073z';
const WA_PATH = 'M17.47 14.38c-.3-.15-1.76-.87-2.03-.97-.27-.1-.47-.15-.67.15-.2.3-.77.97-.94 1.17-.17.2-.35.22-.65.07-.3-.15-1.26-.46-2.4-1.48-.89-.79-1.49-1.77-1.66-2.07-.17-.3-.02-.46.13-.61.13-.13.3-.35.45-.52.15-.17.2-.3.3-.5.1-.2.05-.37-.03-.52-.07-.15-.67-1.61-.92-2.21-.24-.58-.49-.5-.67-.51h-.57c-.2 0-.52.07-.79.37-.27.3-1.04 1.02-1.04 2.48s1.07 2.88 1.21 3.08c.15.2 2.1 3.2 5.08 4.49.71.31 1.26.49 1.69.63.71.23 1.36.19 1.87.12.57-.09 1.76-.72 2.01-1.41.25-.69.25-1.29.17-1.41-.07-.12-.27-.2-.57-.35zM12.05 21.5h-.01a9.4 9.4 0 0 1-4.8-1.32l-.34-.2-3.57.94.95-3.48-.22-.36a9.4 9.4 0 0 1-1.44-5.02c0-5.2 4.23-9.43 9.44-9.43 2.52 0 4.89.98 6.67 2.77a9.37 9.37 0 0 1 2.76 6.67c0 5.2-4.24 9.43-9.44 9.43zm8.03-17.46A11.3 11.3 0 0 0 12.05.7C5.79.7.7 5.79.7 12.05c0 2 .52 3.95 1.52 5.67L.6 23.3l5.72-1.5a11.3 11.3 0 0 0 5.42 1.38h.01c6.26 0 11.35-5.09 11.35-11.35 0-3.03-1.18-5.88-3.32-8.02z';

const INT_LOGOS = {
  whatsapp:   { img:'logos/whatsapp.svg', pad:.14 },
  meta:       { img:'logos/facebook.svg', pad:.14 },
  gcal:       { img:'logos/google-agenda.svg', pad:.14 },
  zap:        { img:'logos/zap.png', pad:.12 },
  vivareal:   { img:'logos/vivareal.png', full:true },
  imovelweb:  { img:'logos/imovelweb.png', pad:.18 },
  chaves:     { img:'logos/chavesnamao.png', full:true },
  'gads-conv':{ img:'logos/google-ads.svg', pad:.18 },
  gads:       { img:'logos/google-ads.svg', pad:.18 },
  tiktok:     { img:'logos/tiktok.svg', full:true },
  instagram:  { img:'logos/instagram.svg', pad:.12 },
  zapsign:    { img:'logos/zapsign.png', full:true },
  clicksign:  { img:'logos/clicksign.png', pad:.1 },
  serasa:     { img:'logos/serasa.png', pad:.12 },
  boavista:   { img:'logos/boavista.png', full:true },
  quod:       { img:'logos/quod.png', pad:.18 },
  asaas:      { img:'logos/asaas.png', full:true },
  vista:      { img:'logos/vista.png', pad:.16 },
  jetimob:    { img:'logos/jetimob.png', pad:.08 },
  kenlo:      { img:'logos/kenlo.png', pad:.12 },
  imobzi:     { img:'logos/imobzi.png', pad:.08 },
  cvcrm:      { img:'logos/cvcrm.png', full:true },
  api4com:    { img:'logos/api4com.svg', pad:.14 },
  outlook:    { img:'logos/outlook.svg', pad:.14 },
  gmail:      { img:'logos/gmail.svg', pad:.18 },
  zapier:     { img:'logos/zapier.png', full:true },
  make:       { img:'logos/make.png', pad:.08 },
  api:        { img:'favicon.svg', pad:.16 },
};

function IntLogo({ app, size = 40 }) {
  const r = Math.round(size * 0.22);
  const box = { width:size, height:size, borderRadius:r, flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden' };
  const s = Math.round(size * 0.56);
  // Logos oficiais baixados para logos/ (2026-10-04). full = ícone que já ocupa o quadrado; senão vai num quadro branco
  const L = INT_LOGOS[app.id];
  if (L && L.imgs) {
    const cell = Math.floor((size - 2 - 4 - 2) / 2);   // borda 1+1, padding 2+2, gap 2
    return (
      <div style={{ ...box, background:'#fff', border:'1px solid var(--line)', flexWrap:'wrap', alignContent:'center', gap:2, padding:2, boxSizing:'border-box' }}>
        {L.imgs.map(src => <img key={src} src={src} alt="" style={{ width:cell, height:cell, objectFit:'contain', borderRadius:Math.round(cell * 0.2) }} />)}
      </div>
    );
  }
  if (L && L.img) {
    return L.full
      ? <div style={{ ...box }}><img src={L.img} alt={app.name} style={{ width:'100%', height:'100%', objectFit:'cover' }} /></div>
      : <div style={{ ...box, background:'#fff', border:'1px solid var(--line)', padding:Math.round(size * (L.pad ?? 0.16)), boxSizing:'border-box' }}><img src={L.img} alt={app.name} style={{ width:'100%', height:'100%', objectFit:'contain' }} /></div>;
  }
  switch (app.logo) {
    case 'meta':
      return <div style={{ ...box, background:'#1877f2' }}><svg width={s} height={s} viewBox="0 0 24 24" fill="#fff"><path d={FB_PATH}/></svg></div>;
    case 'whatsapp':
      return <div style={{ ...box, background: app.tag === 'oficial' ? '#075e54' : '#25D366' }}><svg width={s} height={s} viewBox="0 0 24 24" fill="#fff"><path d={WA_PATH}/></svg></div>;
    case 'gcal':
      return (
        <div style={{ ...box, background:'#fff', border:'1px solid var(--line)' }}>
          <svg width={size*0.7} height={size*0.7} viewBox="0 0 48 48" fill="none">
            <rect x="4" y="6" width="40" height="38" rx="6" fill="#fff" stroke="#dadce0" strokeWidth="2"/>
            <path d="M4 12a6 6 0 0 1 6-6h28a6 6 0 0 1 6 6v4H4z" fill="#1a73e8"/>
            <text x="24" y="38" textAnchor="middle" fill="#1a73e8" fontSize="17" fontWeight="700" fontFamily="Arial, sans-serif">31</text>
          </svg>
        </div>
      );
    case 'gads':
      return (
        <div style={{ ...box, background:'#fff', border:'1px solid var(--line)' }}>
          <svg width={s} height={s} viewBox="0 0 24 24">
            <path d="M9.2 3.6 2.6 15a3.3 3.3 0 0 0 5.7 3.3l6.6-11.4z" fill="#fbbc04"/>
            <path d="M14.8 3.6a3.3 3.3 0 0 0-5.7 3.3l6.6 11.4a3.3 3.3 0 0 0 5.7-3.3z" fill="#4285f4"/>
            <circle cx="5.5" cy="16.7" r="3.3" fill="#34a853"/>
          </svg>
        </div>
      );
    case 'gmail':
      return (
        <div style={{ ...box, background:'#fff', border:'1px solid var(--line)' }}>
          <svg width={s} height={s} viewBox="0 0 24 24">
            <path d="M2 6.5 12 13l10-6.5V18a2 2 0 0 1-2 2h-2.5V9.8L12 13.5 6.5 9.8V20H4a2 2 0 0 1-2-2z" fill="#4285f4"/>
            <path d="M2 6.5V6a2 2 0 0 1 3.2-1.6L12 9.3l6.8-4.9A2 2 0 0 1 22 6v.5L12 13z" fill="#ea4335"/>
          </svg>
        </div>
      );
    case 'insta':
      return (
        <div style={{ ...box, background:'linear-gradient(45deg,#f9a825,#e4405f 45%,#833ab4)' }}>
          <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2"><rect x="3" y="3" width="18" height="18" rx="5"/><circle cx="12" cy="12" r="4"/><circle cx="17.3" cy="6.7" r="1" fill="#fff" stroke="none"/></svg>
        </div>
      );
    case 'text':
      return <div style={{ ...box, background:app.bg, color:app.fg || '#fff', fontWeight:800, fontSize:Math.round(size*0.36), letterSpacing:'-.02em' }}>{app.mark}</div>;
    default: // apps do Horos: azul do Horos · parceiros sem marca definida: grafite
      return (
        <div style={{ ...box, background: app.native ? 'var(--accent)' : '#3a3f47', color:'#fff' }}>
          <Icon name={app.icon} size={Math.round(size*0.5)} stroke={1.8} />
        </div>
      );
  }
}

// Ícones das metas — traço fino, desenho no estilo do Marketplace (32px)
const INT_GOAL_SVG = {
  atrair: <g><circle cx="16" cy="16" r="10.5"/><path d="M16 3v4M16 25v4M3 16h4M25 16h4"/><circle cx="16" cy="13.6" r="2.3"/><path d="M12 20.2a4 4 0 0 1 8 0"/></g>,
  qualificar: <g><path d="M5 6h22l-8.5 10.5V24l-5 3v-10.5Z"/><path d="M9 10h14"/></g>,
  nutrir: <g><path d="M16 28V15"/><path d="M16 15c0-5-3.5-8.5-10-8.5 0 5.5 3.5 9 10 8.5Z"/><path d="M16 15c0-5 3.5-8.5 10-8.5 0 5.5-3.5 9-10 8.5Z"/><path d="M16 15c-1.5-2.6-1.5-5.4 0-8 1.5 2.6 1.5 5.4 0 8Z"/></g>,
  comunicar: <g><path d="M16 5c6.6 0 12 4.6 12 10.4S22.6 25.8 16 25.8c-1.6 0-3.1-.3-4.5-.8L5 27.5l1.9-5.2A10 10 0 0 1 4 15.4C4 9.6 9.4 5 16 5Z"/><circle cx="11.5" cy="15.4" r="1.1"/><circle cx="16" cy="15.4" r="1.1"/><circle cx="20.5" cy="15.4" r="1.1"/></g>,
  contratos: <g><path d="M19 4H8.5A1.5 1.5 0 0 0 7 5.5v21A1.5 1.5 0 0 0 8.5 28h15a1.5 1.5 0 0 0 1.5-1.5V10Z"/><path d="M19 4v6h6"/><path d="M11 20.5c1.2-2.5 2.2-3.6 2.8-3.2.8.5-.6 3.7.3 4 .8.3 1.8-2.6 2.6-2.3.7.3.2 2 1 2.1.6.1 1.3-.7 2.3-1.1"/></g>,
  pagamentos: <g><path d="M7 4h18v24l-2.3-1.6-2.2 1.6-2.3-1.6-2.2 1.6-2.3-1.6-2.2 1.6L9.3 26.4 7 28Z"/><path d="M19 11.2c-.5-1-1.6-1.6-3-1.6-1.8 0-3 .9-3 2.2 0 3.2 6 1.6 6 4.8 0 1.3-1.3 2.3-3 2.3-1.5 0-2.7-.6-3.2-1.7"/><path d="M16 8v2M16 19v2"/></g>,
  agenda: <g><rect x="7" y="6" width="18" height="22" rx="1.5"/><path d="M12 4h8v4h-8Z"/><path d="m10.5 14 1.5 1.5 2.5-2.5M17 14h4.5M10.5 20.5l1.5 1.5 2.5-2.5M17 20.5h4.5"/></g>,
  automatizar: <g><circle cx="16" cy="16" r="4"/><path d="M14 4h4l.7 3.4 2.4 1 2.9-1.9 2.8 2.8-1.9 2.9 1 2.4L29.3 15v4l-3.4.7-1 2.4 1.9 2.9-2.8 2.8-2.9-1.9-2.4 1L18 29.3h-4l-.7-3.4-2.4-1-2.9 1.9-2.8-2.8 1.9-2.9-1-2.4L2.7 19v-4l3.4-.7 1-2.4-1.9-2.9 2.8-2.8 2.9 1.9 2.4-1Z" transform="translate(0 -1.3)"/></g>,
  equipe: <g><rect x="3.5" y="7" width="25" height="18" rx="1.5"/><circle cx="11" cy="14" r="2.6"/><path d="M6.8 21c.6-2.3 2.2-3.5 4.2-3.5s3.6 1.2 4.2 3.5"/><path d="M18.5 13h6M18.5 16.5h6M18.5 20h4"/></g>,
  atender: <g><path d="M6 18v-3a10 10 0 0 1 20 0v3"/><rect x="4" y="17" width="5" height="8" rx="2"/><rect x="23" y="17" width="5" height="8" rx="2"/><path d="M25.5 25c0 2.5-2.5 3.5-6 3.5H17"/></g>,
  imoveis: <g><path d="M4 28h24"/><path d="M7 28V13l9-7 9 7v15"/><rect x="13" y="19" width="6" height="9"/><path d="M11 14h2M19 14h2"/></g>,
  outros: <g><path d="m16 3.5 5 8.5H11Z"/><rect x="4.5" y="17" width="10" height="10" rx="1"/><circle cx="22.5" cy="22" r="5"/></g>,
};

function IntGoalIcon({ id }) {
  return (
    <svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round">
      {INT_GOAL_SVG[id]}
    </svg>
  );
}

const INT_GOALS = [
  { id:'atrair',     label:'Atrair novos leads'         },
  { id:'qualificar', label:'Qualificar leads'           },
  { id:'nutrir',     label:'Nutrir leads'               },
  { id:'comunicar',  label:'Comunicar com leads'        },
  { id:'contratos',  label:'Gerenciar contratos'        },
  { id:'pagamentos', label:'Pagamentos e cobranças'     },
  { id:'agenda',     label:'Organizar agenda e visitas' },
  { id:'imoveis',    label:'Imóveis e estoque'          },
  { id:'automatizar',label:'Automatizar funil e dados'  },
  { id:'atender',    label:'Atender clientes'           },
  { id:'outros',     label:'Outros usos'                },
];

// Só aplicativos externos (nada de telas/ferramentas internas do Horos — simuladores e afins ficam de
// fora, viram "lista de futuros" fora da tela; ver CLAUDE.md). kind: 'meta' | 'google' | 'whatsapp' |
// 'open' (API, webhooks, CAPI — IntOpenPanel) | 'partner' (token da conta do cliente — IntPartnerBlock) |
// 'portal_email' (endereço @leads.horos.sale — IntPortalEmailBlock) | 'properties' (estoque de imóveis —
// IntPropertiesBlock) | 'api4com' (IntApi4ComBlock) | 'gads_conv' (IntGadsConvBlock) | 'soon'
// WhatsApp é um card só: ao conectar, a janela mostra as duas formas — oficial (API do Meta) e não oficial (QR code)
// Ordem dos planejados = ordem de construção combinada com o usuário (2026-10-03)
// segments: 'all' (todo ramo) ou array de ids de HOROS_SEGMENTS (horos-segments.js) — filtra o catálogo
// pelo ramo do workspace (appInSegment/useHorosSegment). Integração nova → marcar o(s) ramo(s) certo(s);
// na dúvida, 'all'. O que já está conectado nunca some, mesmo fora do ramo atual (ver `installed`).
const INT_APPS = [
  { id:'whatsapp', name:'WhatsApp', by:'Meta', logo:'whatsapp', kind:'whatsapp', native:true, segments:'all',
    desc:'Converse com seus clientes dentro do Horos. Conecte pela API oficial do Meta ou pelo QR code.',
    goals:['comunicar','atender','nutrir'], keywords:'mensagens chat conversa whats zap qr code não oficial evolution' },
  { id:'meta', name:'Facebook e Instagram Ads', by:'Meta', logo:'meta', kind:'meta', native:true, segments:'all',
    desc:'Leads dos formulários dos anúncios entram no Inbox, e as vendas voltam para o Facebook otimizar as campanhas.',
    goals:['atrair','automatizar'], keywords:'facebook instagram meta anúncios ads formulário lead form conversões conversions api capi pixel conjunto de dados venda' },
  { id:'gcal', name:'Google Agenda', by:'Google', logo:'gcal', kind:'google', native:true, segments:'all',
    desc:'Sincronize visitas e reuniões da Agenda do Horos com o Google Agenda.',
    goals:['agenda'], keywords:'calendário google agenda visitas reuniões' },

  // 1. Portais — e-mail de aviso de lead (IntPortalEmailBlock, api/portal-lead-webhook.js) — só imobiliário
  { id:'zap', name:'ZAP Imóveis', beta:true, by:'Grupo OLX · Canal Pro', logo:'text', mark:'ZAP', bg:'#e8541c', kind:'portal_email', segments:['imobiliario'],
    desc:'Leads dos seus anúncios no ZAP Imóveis entram no funil sem digitar.', goals:['atrair','imoveis'], keywords:'portal zap imóveis olx canal pro anúncio e-mail' },
  { id:'vivareal', name:'VivaReal', beta:true, by:'Grupo OLX · Canal Pro', logo:'text', mark:'VR', bg:'#1190cd', kind:'portal_email', segments:['imobiliario'],
    desc:'Leads dos seus anúncios no VivaReal entram no funil sem digitar.', goals:['atrair','imoveis'], keywords:'portal vivareal viva real olx canal pro anúncio e-mail' },
  { id:'imovelweb', name:'Imovelweb', beta:true, by:'Imovelweb', logo:'text', mark:'iw', bg:'#ff6a00', kind:'portal_email', segments:['imobiliario'],
    desc:'Contatos dos anúncios no Imovelweb direto no Inbox do CRM.', goals:['atrair'], keywords:'portal imovelweb anúncio e-mail' },
  { id:'chaves', name:'Chaves na Mão', beta:true, by:'Chaves na Mão', logo:'text', mark:'CM', bg:'#f39200', kind:'portal_email', segments:['imobiliario'],
    desc:'Leads do Chaves na Mão chegando sozinhos no seu funil.', goals:['atrair'], keywords:'portal chaves na mão anúncio e-mail' },

  // 2. Conversões de volta para os anúncios
  { id:'gads-conv', name:'Conversões do Google Ads', beta:true, by:'Google', logo:'gads', kind:'gads_conv', segments:'all',
    desc:'Envia as vendas do Horos para o Google Ads otimizar as campanhas pelo que dá resultado.', goals:['automatizar','atrair'], keywords:'google ads conversão offline gclid' },

  // 3. Mais fontes de anúncio
  { id:'gads', name:'Google Ads (formulários)', by:'Google', logo:'gads', kind:'open', segments:'all',
    desc:'Leads dos formulários das campanhas do Google direto no CRM.', goals:['atrair'], keywords:'google anúncios campanhas lead form' },
  { id:'tiktok', name:'TikTok Lead Ads', by:'TikTok', logo:'text', mark:'TT', bg:'#111111', kind:'soon', segments:'all',
    desc:'Leads dos anúncios do TikTok direto no Inbox do CRM.', goals:['atrair'], keywords:'tiktok anúncios lead ads' },

  // 4. Canais oficiais
  { id:'instagram', name:'Instagram Direct', by:'Meta', logo:'insta', kind:'soon', segments:'all',
    desc:'Responda as mensagens do Direct junto com o WhatsApp.', goals:['comunicar','atender'], keywords:'instagram direct dm' },

  // 5. Fechar a venda
  { id:'zapsign', name:'ZapSign', beta:true, by:'ZapSign', logo:'text', mark:'ZS', bg:'#3b5bdb', kind:'partner', segments:'all',
    desc:'Envie proposta, ficha e contrato para o cliente assinar pelo celular.', goals:['contratos'], keywords:'assinatura digital contrato proposta zapsign' },
  { id:'clicksign', name:'Clicksign', beta:true, by:'Clicksign', logo:'text', mark:'CS', bg:'#1c9a6b', kind:'partner', segments:'all',
    desc:'Assinatura eletrônica de contratos com validade jurídica.', goals:['contratos'], keywords:'assinatura digital contrato clicksign d4sign' },
  { id:'serasa', name:'Serasa Experian', by:'Serasa · consulta de crédito', logo:'text', mark:'S', bg:'#1d4f91', kind:'soon', segments:['imobiliario','credito','consorcio','veiculos'],
    desc:'Consulte o score e as restrições do cliente usando a autorização que ele já assinou no Horos.', goals:['qualificar'], keywords:'serasa experian crédito score restrição bacen' },
  { id:'boavista', name:'Boa Vista (Equifax)', by:'Boa Vista SCPC', logo:'text', mark:'BV', bg:'#9e1b32', kind:'soon', segments:['imobiliario','credito','consorcio','veiculos'],
    desc:'Score e pendências do SCPC na ficha do cliente, com a autorização dele.', goals:['qualificar'], keywords:'boa vista scpc equifax crédito score' },
  { id:'quod', name:'Quod', by:'Quod · bureau de crédito', logo:'text', mark:'Q', bg:'#5b2a86', kind:'soon', segments:['imobiliario','credito','consorcio','veiculos'],
    desc:'Score do Cadastro Positivo para qualificar o cliente antes da análise.', goals:['qualificar'], keywords:'quod cadastro positivo crédito score' },
  { id:'asaas', name:'Asaas', beta:true, by:'Asaas · Pix e boleto', logo:'text', mark:'as', bg:'#0030b9', kind:'partner', segments:'all',
    desc:'Cobre sinal, reserva e assessoria por Pix ou boleto e veja quando foi pago.', goals:['pagamentos'], keywords:'pix boleto cobrança pagamento asaas sinal' },

  // 6. Estoque de imóveis — só imobiliário
  { id:'vista', name:'Vista', beta:true, by:'Vista Soft', logo:'text', mark:'V', bg:'#e35205', kind:'properties', segments:['imobiliario'],
    desc:'Traga os imóveis do Vista e mostre o imóvel de interesse dentro do lead.', goals:['imoveis'], keywords:'vista soft sistema imobiliária imóveis estoque' },
  { id:'jetimob', name:'Jetimob', beta:true, by:'Jetimob', logo:'text', mark:'J', bg:'#2b59c3', kind:'properties', segments:['imobiliario'],
    desc:'Imóveis do Jetimob dentro do Horos, ligados aos leads interessados.', goals:['imoveis'], keywords:'jetimob sistema imobiliária imóveis estoque site' },
  { id:'kenlo', name:'Kenlo', beta:true, by:'Kenlo (antigo InGaia)', logo:'text', mark:'K', bg:'#7b2cbf', kind:'properties', segments:['imobiliario'],
    desc:'Estoque de imóveis do Kenlo Imob no Horos, com o imóvel de interesse no lead.', goals:['imoveis'], keywords:'kenlo ingaia sistema imobiliária imóveis estoque' },
  { id:'imobzi', name:'Imobzi', beta:true, by:'Imobzi', logo:'text', mark:'iz', bg:'#00a6a6', kind:'properties', segments:['imobiliario'],
    desc:'Traga os imóveis do Imobzi e cruze com o que cada lead procura.', goals:['imoveis'], keywords:'imobzi sistema imobiliária imóveis estoque' },
  { id:'cvcrm', name:'CV CRM', beta:true, by:'Construtor de Vendas', logo:'text', mark:'CV', bg:'#1e3a8a', kind:'properties', segments:['imobiliario'],
    desc:'Espelho de vendas e unidades disponíveis dos lançamentos das construtoras.', goals:['imoveis'], keywords:'construtora lançamento espelho de vendas unidades cv crm' },

  // 7. Ligações e rotina
  { id:'api4com', name:'Api4Com', beta:true, by:'Api4Com · telefonia', logo:null, icon:'phone', kind:'api4com', segments:'all',
    desc:'Ligue com um clique, grave a chamada e registre no histórico do lead.', goals:['comunicar','atender'], keywords:'ligação telefone voip api4com zenvia gravação' },
  { id:'outlook', name:'Outlook', by:'Microsoft 365', logo:'text', mark:'O', bg:'#0078d4', kind:'soon', segments:'all',
    desc:'Agenda e e-mail da Microsoft integrados ao Horos.', goals:['agenda','comunicar'], keywords:'outlook microsoft 365 agenda email' },
  { id:'gmail', name:'Gmail', beta:true, by:'Google', logo:'gmail', kind:'gmail', segments:'all',
    desc:'Envie e-mails para os leads pela sua conta do Gmail, sem sair da ficha.', goals:['comunicar'], keywords:'email e-mail gmail' },


  // 9. Para todo o resto
  { id:'zapier', name:'Zapier', by:'Zapier', logo:'text', mark:'zap', bg:'#ff4a00', kind:'open', segments:'all',
    desc:'Conecte o Horos a milhares de aplicativos pelo Zapier, sem programar.', goals:['automatizar','outros'], keywords:'zapier zap automação integração apps webhook' },
  { id:'make', name:'Make', by:'Make (antigo Integromat)', logo:'text', mark:'M', bg:'#6d00cc', kind:'open', segments:'all',
    desc:'Monte cenários no Make que recebem e enviam leads do Horos, sem programar.', goals:['automatizar','outros'], keywords:'make integromat cenário automação integração apps webhook' },
  { id:'api', name:'API e webhooks', by:'Horos', logo:null, icon:'link', kind:'open', segments:'all',
    desc:'Para desenvolvedores e parceiros: crie sua própria integração com o Horos.', goals:['automatizar','outros'], keywords:'api webhook desenvolvedor parceiro integração' },
];
function appInSegment(app, seg) {
  return app.segments === 'all' || !app.segments || (Array.isArray(app.segments) && app.segments.includes(seg));
}

// Conteúdo da janela de cada app (Descrição / Instalação / Recursos)
const INT_DETAILS = {
  whatsapp: {
    color:'#25D366',
    tagline:'Converse com seus clientes no WhatsApp sem sair do Horos e leve cada conversa para o funil.',
    paragraphs:[
      'Conecte o WhatsApp ao Horos e tenha todas as conversas, fotos, áudios e documentos dos seus clientes no mesmo lugar onde você acompanha o funil.',
      'Há duas formas de conectar: pela API oficial do WhatsApp Business (Meta), mais estável e indicada para disparos, ou pelo QR code, que usa o WhatsApp Web do seu celular e conecta em um minuto. Você escolhe ao clicar em Conectar.',
      'Cada conversa fica ligada ao lead: você vê o histórico completo, muda a etapa e marca etiquetas sem trocar de aba. As etiquetas que você usa no celular aparecem no Horos, e vice-versa.',
    ],
    listTitle:'Com o WhatsApp no Horos você pode:',
    features:['Responder clientes direto do CRM e do Horos 360','Ver o histórico completo da conversa ao lado do lead','Adicionar o contato como lead com um clique','Usar respostas rápidas com texto, fotos e áudios','Etiquetar conversas e ver as etiquetas nos cards do Kanban','Enviar disparos em massa com ritmo seguro'],
    slides:[
      { icon:'message', title:'Conversas no CRM', text:'Fotos, áudios, vídeos e documentos' },
      { icon:'tag',     title:'Etiquetas sincronizadas', text:'As mesmas do seu celular' },
      { icon:'bolt',    title:'Respostas rápidas', text:'Digite / e escolha a resposta' },
      { icon:'send',    title:'Disparos em massa', text:'Ritmo seguro e horário comercial' },
    ],
    setup:['Clique em Conectar e escolha a forma: API oficial do Meta ou QR code.','API oficial: clique em "Conectar com a Meta", entre com o Facebook, escolha ou crie a conta do WhatsApp Business e confirme o número com o código que chega por SMS ou ligação.','QR code: você vai para Mensagens. No celular, abra o WhatsApp › Aparelhos conectados › Conectar aparelho e escaneie o código.','Pronto: as conversas novas começam a aparecer em Mensagens.'],
    requires:['QR code: celular com WhatsApp ou WhatsApp Business e internet no momento da conexão','API oficial: um número que não esteja em uso no aplicativo do WhatsApp, acesso ao Facebook da empresa e um cartão cadastrado no WhatsApp Manager (o Meta cobra os modelos de mensagem direto da sua conta)'],
    help:{ label:'Central de ajuda do WhatsApp', url:'https://faq.whatsapp.com/' },
  },
  zap: {
    color:'#e8541c',
    tagline:'O aviso de lead que o ZAP Imóveis já manda por e-mail entra sozinho no Inbox do CRM.',
    paragraphs:[
      'O Horos não tem parceria direta com o Grupo OLX — ele lê o e-mail de aviso de lead que o ZAP Imóveis já manda para você e cria o lead a partir dele, sem precisar digitar nada.',
      'Basta cadastrar o endereço que o Horos gera para você no painel do anunciante (Canal Pro), como um e-mail de notificação de lead a mais.',
      'Nome, telefone e e-mail são lidos de dentro da mensagem. Se o layout do e-mail mudar e algo não entrar certinho, chame o suporte — a leitura é ajustável.',
    ],
    listTitle:'Com o ZAP Imóveis no Horos você pode:',
    features:['Lead do anúncio direto no Inbox, sem digitar','Telefone repetido não vira lead duplicado','Lead arquivado com o mesmo telefone volta pro funil','Etiqueta de origem ZAP Imóveis no card e na ficha'],
    slides:[
      { icon:'mail',     title:'Chega por e-mail', text:'O aviso que o ZAP já manda' },
      { icon:'userPlus', title:'Lead no Inbox', text:'Sem digitar nada' },
    ],
    setup:['Copie o endereço na aba Instalação.','No Canal Pro (painel do anunciante usado pelo ZAP Imóveis), abra as configurações de notificação de lead e cadastre o endereço acima como destinatário.','O próximo contato que chegar por esse anúncio já aparece no Inbox do CRM, com a etiqueta ZAP Imóveis.'],
    requires:['Conta de anunciante ativa no ZAP Imóveis','Acesso ao Canal Pro para cadastrar o e-mail de notificação de lead'],
    help:{ label:'Site do ZAP Imóveis', url:'https://www.zapimoveis.com.br' },
  },
  vivareal: {
    color:'#1190cd',
    tagline:'O aviso de lead que o VivaReal já manda por e-mail entra sozinho no Inbox do CRM.',
    paragraphs:[
      'O Horos não tem parceria direta com o Grupo OLX — ele lê o e-mail de aviso de lead que o VivaReal já manda para você e cria o lead a partir dele, sem precisar digitar nada.',
      'Basta cadastrar o endereço que o Horos gera para você no painel do anunciante (Canal Pro), como um e-mail de notificação de lead a mais.',
      'Nome, telefone e e-mail são lidos de dentro da mensagem. Se o layout do e-mail mudar e algo não entrar certinho, chame o suporte — a leitura é ajustável.',
    ],
    listTitle:'Com o VivaReal no Horos você pode:',
    features:['Lead do anúncio direto no Inbox, sem digitar','Telefone repetido não vira lead duplicado','Lead arquivado com o mesmo telefone volta pro funil','Etiqueta de origem VivaReal no card e na ficha'],
    slides:[
      { icon:'mail',     title:'Chega por e-mail', text:'O aviso que o VivaReal já manda' },
      { icon:'userPlus', title:'Lead no Inbox', text:'Sem digitar nada' },
    ],
    setup:['Copie o endereço na aba Instalação.','No Canal Pro (painel do anunciante usado pelo VivaReal), abra as configurações de notificação de lead e cadastre o endereço acima como destinatário.','O próximo contato que chegar por esse anúncio já aparece no Inbox do CRM, com a etiqueta VivaReal.'],
    requires:['Conta de anunciante ativa no VivaReal','Acesso ao Canal Pro para cadastrar o e-mail de notificação de lead'],
    help:{ label:'Site do VivaReal', url:'https://www.vivareal.com.br' },
  },
  imovelweb: {
    color:'#ff6a00',
    tagline:'O aviso de lead que o Imovelweb já manda por e-mail entra sozinho no Inbox do CRM.',
    paragraphs:[
      'O Horos não tem parceria direta com o Imovelweb — ele lê o e-mail de aviso de lead que o portal já manda para você e cria o lead a partir dele, sem precisar digitar nada.',
      'Basta cadastrar o endereço que o Horos gera para você no painel do anunciante do Imovelweb, como um e-mail de notificação de lead a mais.',
      'Nome, telefone e e-mail são lidos de dentro da mensagem. Se o layout do e-mail mudar e algo não entrar certinho, chame o suporte — a leitura é ajustável.',
    ],
    listTitle:'Com o Imovelweb no Horos você pode:',
    features:['Contato do anúncio direto no Inbox, sem digitar','Telefone repetido não vira lead duplicado','Lead arquivado com o mesmo telefone volta pro funil','Etiqueta de origem Imovelweb no card e na ficha'],
    slides:[
      { icon:'mail',     title:'Chega por e-mail', text:'O aviso que o Imovelweb já manda' },
      { icon:'userPlus', title:'Lead no Inbox', text:'Sem digitar nada' },
    ],
    setup:['Copie o endereço na aba Instalação.','No painel do anunciante do Imovelweb, abra as configurações de e-mail de notificação e adicione o endereço acima.','O próximo contato do Imovelweb já aparece no Inbox do CRM, com a etiqueta Imovelweb.'],
    requires:['Conta de anunciante ativa no Imovelweb','Acesso ao painel para cadastrar o e-mail de notificação de lead'],
    help:{ label:'Site do Imovelweb', url:'https://www.imovelweb.com.br' },
  },
  chaves: {
    color:'#f39200',
    tagline:'O aviso de lead que o Chaves na Mão já manda por e-mail entra sozinho no Inbox do CRM.',
    paragraphs:[
      'O Horos não tem parceria direta com o Chaves na Mão — ele lê o e-mail de aviso de lead que o portal já manda para você e cria o lead a partir dele, sem precisar digitar nada.',
      'Basta cadastrar o endereço que o Horos gera para você no painel do anunciante do Chaves na Mão, como um e-mail de notificação de lead a mais.',
      'Nome, telefone e e-mail são lidos de dentro da mensagem. Se o layout do e-mail mudar e algo não entrar certinho, chame o suporte — a leitura é ajustável.',
    ],
    listTitle:'Com o Chaves na Mão no Horos você pode:',
    features:['Contato do anúncio direto no Inbox, sem digitar','Telefone repetido não vira lead duplicado','Lead arquivado com o mesmo telefone volta pro funil','Etiqueta de origem Chaves na Mão no card e na ficha'],
    slides:[
      { icon:'mail',     title:'Chega por e-mail', text:'O aviso que o Chaves na Mão já manda' },
      { icon:'userPlus', title:'Lead no Inbox', text:'Sem digitar nada' },
    ],
    setup:['Copie o endereço na aba Instalação.','No painel do anunciante do Chaves na Mão, abra as configurações de e-mail de notificação e adicione o endereço acima.','O próximo contato do Chaves na Mão já aparece no Inbox do CRM, com a etiqueta Chaves na Mão.'],
    requires:['Conta de anunciante ativa no Chaves na Mão','Acesso ao painel para cadastrar o e-mail de notificação de lead'],
    help:{ label:'Central de ajuda do Chaves na Mão', url:'https://www.chavesnamao.com.br/central-de-ajuda/' },
  },
  meta: {
    color:'#1877f2',
    tagline:'Os leads dos seus anúncios no Facebook e no Instagram chegam no Horos em segundos, e as vendas voltam para o anúncio aprender a achar quem compra.',
    paragraphs:[
      'Conecte as páginas do Facebook que você usa nos anúncios. Cada vez que alguém preencher um formulário de cadastro (Lead Form), o contato entra no Inbox do CRM com nome, telefone, e-mail e as respostas do formulário, já com o selo "Meta Ads". Telefone repetido não vira lead duplicado.',
      'Opcional: ligue as conversões. Quando um lead dos anúncios avança no funil (visita, crédito aprovado, venda), o Horos avisa o Facebook pela Conversions API. As campanhas passam a otimizar para quem de fato compra, e não só para quem preenche o formulário. A venda vai como "Purchase", com o valor do imóvel.',
    ],
    listTitle:'Com o Facebook e Instagram Ads no Horos você pode:',
    features:['Receber os leads dos anúncios em segundos, sem planilha','Saber de qual anúncio e canal o lead veio','Conectar mais de uma página do Facebook','Mandar visitas, créditos aprovados e vendas de volta para o Facebook','Escolher quais etapas do funil viram evento'],
    slides:[
      { icon:'userPlus', title:'Lead chega sozinho', text:'Direto no Inbox do CRM' },
      { icon:'target',   title:'Origem marcada', text:'Selo Meta Ads no card' },
      { icon:'activity', title:'Venda volta para o anúncio', text:'Conversões (opcional)' },
    ],
    setup:['Clique em Autorizar, entre com o Facebook e permita o acesso às páginas e aos leads.','Escolha a página que recebe os leads dos anúncios. Pronto: o próximo lead do formulário entra no Inbox.','Conversões (opcional): no Gerenciador de Eventos do Meta, abra o conjunto de dados (pixel) dos anúncios, copie o ID e, em Configurações › Conversions API, gere um token de acesso. Cole os dois em "Conversões" acima, escolha as etapas e salve.'],
    requires:['Ser administrador da página no Facebook','Anúncio com formulário de cadastro (Lead Form)','Para as conversões: acesso de administrador ao conjunto de dados (pixel)'],
    help:{ label:'Ajuda do Meta para Empresas', url:'https://www.facebook.com/business/help' },
  },
  gcal: {
    color:'#1a73e8',
    tagline:'Suas visitas e reuniões do Horos no Google Agenda, sem marcar compromisso duas vezes.',
    paragraphs:[
      'Conecte sua conta Google e os compromissos criados na Agenda do Horos vão para o seu Google Agenda, aparecendo no celular com lembretes.',
      'Os eventos do Google também aparecem na Agenda do Horos, então você enxerga o dia inteiro antes de marcar uma visita.',
    ],
    listTitle:'Com o Google Agenda no Horos você pode:',
    features:['Enviar visitas e reuniões para o Google Agenda','Ver seus eventos do Google na Agenda do Horos','Receber os lembretes do Google no celular','Desconectar quando quiser'],
    slides:[
      { icon:'cal',    title:'Agenda sincronizada', text:'Horos e Google lado a lado' },
      { icon:'bell',   title:'Lembretes no celular', text:'Pelo app do Google Agenda' },
      { icon:'mapPin', title:'Visitas com endereço', text:'Abra no mapa com um toque' },
    ],
    setup:['Clique em Autorizar e escolha sua conta Google.','Permita o acesso à agenda.','Pronto: abra a Agenda do Horos e veja os eventos juntos.'],
    requires:['Conta Google (Gmail ou Google Workspace)'],
    help:{ label:'Ajuda do Google Agenda', url:'https://support.google.com/calendar' },
  },
  api: {
    color:'#1F93FF',
    tagline:'Crie sua própria integração: mande leads para o Horos e receba um aviso a cada mudança no funil.',
    paragraphs:[
      'Gere uma chave e use a API do Horos para criar, buscar e atualizar leads a partir de qualquer sistema: site, landing page, portal, planilha ou sistema da construtora.',
      'Com os webhooks, o Horos avisa o endereço que você escolher quando um lead entra, muda de etapa, é vendido ou arquivado. Cada aviso vai assinado (cabeçalho X-Horos-Signature) para o seu sistema conferir que veio do Horos.',
      'Telefone repetido não vira lead duplicado, e o lead que estava arquivado volta para o funil.',
    ],
    listTitle:'Com a API e os webhooks você pode:',
    features:['Criar leads de qualquer formulário ou sistema','Buscar e atualizar leads (etapa, temperatura, valor)','Receber avisos de lead novo, mudança de etapa, venda e arquivamento','Usar uma chave só sua ou uma da imobiliária toda'],
    slides:[
      { icon:'userPlus', title:'Leads de qualquer lugar', text:'POST /api/v1/leads' },
      { icon:'bell',     title:'Avisos em tempo real', text:'Webhooks assinados' },
      { icon:'shield',   title:'Chave revogável', text:'Desligue quando quiser' },
    ],
    setup:['Na seção Chaves de API, clique em Gerar chave e copie: ela aparece uma vez só.','Envie a chave no cabeçalho Authorization: Bearer hrs_… em cada chamada para https://horos.sale/api/v1.','Para receber avisos, adicione um webhook com o endereço do seu sistema e escolha os eventos.','Use Testar para mandar um aviso de exemplo e conferir se chegou.'],
    requires:['Alguém que saiba programar ou uma ferramenta de automação (Zapier, Make, n8n)','Para webhooks: um endereço https que aceite POST'],
    help:{ label:'Documentação da API', url:'about-integrations.html#api' },
  },
  zapier: {
    color:'#ff4a00',
    tagline:'Ligue o Horos a milhares de aplicativos pelo Zapier, sem programar.',
    paragraphs:[
      'Para mandar leads de outro aplicativo para o Horos (Typeform, RD Station, planilha do Google, site), crie um Zap com a ação "Webhooks by Zapier → POST" chamando a API do Horos com a sua chave.',
      'Para fazer algo quando acontece uma coisa no Horos (lead novo, venda), crie um Zap com o gatilho "Webhooks by Zapier → Catch Hook", copie o endereço que ele gera e cadastre aqui como webhook.',
    ],
    listTitle:'Exemplos do que dá para fazer:',
    features:['Lead do Typeform, RD Station ou do site entrando no Inbox','Venda no Horos gerando linha numa planilha do Google','Lead novo avisando um grupo no Slack ou por e-mail'],
    slides:[
      { icon:'bolt',   title:'Sem programar', text:'Webhooks by Zapier' },
      { icon:'layers', title:'Milhares de apps', text:'Planilhas, formulários, e-mail' },
    ],
    setup:['Gere uma chave do Zapier abaixo.','Para ENVIAR leads ao Horos: ação "Webhooks by Zapier → POST" para https://horos.sale/api/v1/leads, Payload Type JSON, cabeçalho Authorization: Bearer <sua chave> e os campos name, phone, email, region.','Para RECEBER avisos do Horos: gatilho "Webhooks by Zapier → Catch Hook"; copie o endereço e cadastre em Webhooks abaixo, escolhendo os eventos.','Clique em Testar no webhook para o Zapier enxergar os campos do lead.'],
    requires:['Conta no Zapier em plano que inclua Webhooks by Zapier'],
    help:{ label:'Ajuda do Webhooks by Zapier', url:'https://help.zapier.com/hc/en-us/articles/8496288690317' },
  },
  make: {
    color:'#6d00cc',
    tagline:'Monte cenários no Make que recebem e enviam leads do Horos, sem programar.',
    paragraphs:[
      'Para mandar leads ao Horos, use o módulo "HTTP → Make a request" chamando a API do Horos com a sua chave.',
      'Para começar um cenário quando algo acontece no Horos (lead novo, mudança de etapa, venda), use o gatilho "Webhooks → Custom webhook", copie o endereço e cadastre aqui como webhook.',
    ],
    listTitle:'Exemplos do que dá para fazer:',
    features:['Lead de formulário ou planilha entrando no Inbox','Venda no Horos atualizando o financeiro em outro sistema','Mudança de etapa disparando e-mail ou tarefa'],
    slides:[
      { icon:'bolt',   title:'Sem programar', text:'Módulos HTTP e Webhooks' },
      { icon:'layers', title:'Cenários visuais', text:'Arraste e conecte os passos' },
    ],
    setup:['Gere uma chave do Make abaixo.','Para ENVIAR leads ao Horos: módulo "HTTP → Make a request", método POST, URL https://horos.sale/api/v1/leads, cabeçalho Authorization: Bearer <sua chave>, corpo JSON com name, phone, email, region.','Para RECEBER avisos do Horos: gatilho "Webhooks → Custom webhook"; copie o endereço e cadastre em Webhooks abaixo, escolhendo os eventos.','Clique em Testar no webhook para o Make reconhecer a estrutura dos dados.'],
    requires:['Conta no Make (o plano gratuito já tem HTTP e Webhooks)'],
    help:{ label:'Ajuda do Make: webhooks', url:'https://www.make.com/en/help/tools/webhooks' },
  },
  gads: {
    color:'#4285f4',
    tagline:'Quem preenche o formulário de lead da sua campanha do Google vira lead no Horos na hora.',
    paragraphs:[
      'O Google Ads manda cada lead do formulário (extensão de formulário de lead) para o endereço do Horos. O contato entra no Inbox com nome, telefone, e-mail e as perguntas extras do formulário, com o selo de origem Google Ads e o GCLID guardado.',
      'O teste do Google também cria um lead, com "(teste do Google)" no nome, para você ver que funcionou.',
    ],
    listTitle:'Com o Google Ads no Horos você pode:',
    features:['Receber os leads do formulário em segundos','Guardar o GCLID e a campanha de cada lead','Evitar lead duplicado pelo telefone'],
    slides:[
      { icon:'userPlus', title:'Lead chega sozinho', text:'Direto no Inbox do CRM' },
      { icon:'target',   title:'Campanha guardada', text:'GCLID, campanha e anúncio' },
    ],
    setup:['Clique em Conectar com o Google e entre com a conta que administra os anúncios (permita o acesso ao Google Ads).','Escolha a conta do Google Ads: aparecem os formulários de lead das campanhas.','Marque os formulários e clique em Ligar no Horos. Pronto: o próximo lead entra no Inbox.','Se preferir, use "Configurar manualmente": gere a chave e cole o endereço e a chave no formulário de lead (Opções de entrega de leads › Integração de webhook).'],
    requires:['Campanha do Google Ads com formulário de lead'],
    help:{ label:'Ajuda do Google: webhook de formulário de lead', url:'https://support.google.com/google-ads/answer/9423234' },
  },
  zapsign: {
    color:'#3b5bdb',
    tagline:'Mande proposta, ficha e contrato para o cliente assinar pelo celular, direto da ficha no Horos.',
    paragraphs:[
      'Conecte a conta da sua imobiliária na ZapSign e, na ficha do cliente, aba Contratos e cobranças, escolha o PDF e quem assina (o cliente e os participantes já vêm preenchidos).',
      'A ZapSign manda o link por e-mail e você também pode copiar o link de cada pessoa ou mandar pelo WhatsApp do Horos. Quando todos assinam, o status muda sozinho e o documento assinado fica na ficha.',
    ],
    listTitle:'Com a ZapSign no Horos você pode:',
    features:['Enviar PDF da ficha para assinatura','Assinatura de várias pessoas (comprador, cônjuge, fiador)','Mandar o link pelo WhatsApp do Horos','Ver quem já assinou e baixar o documento assinado','Aviso no sino quando todos assinam'],
    slides:[
      { icon:'pencil', title:'Assinatura pelo celular', text:'Link para cada pessoa' },
      { icon:'check',  title:'Status automático', text:'Assinado aparece na ficha' },
    ],
    setup:['Na ZapSign, abra Configurações › Integração › API e copie o token de API.','Cole o token abaixo e clique em Conectar. O Horos confere o token e tenta cadastrar o aviso de assinatura na sua conta.','Na ficha do cliente, abra a aba Contratos e cobranças e clique em Enviar para assinatura.'],
    requires:['Conta na ZapSign com acesso à API','Ser da diretoria ou quem criou a imobiliária no Horos para conectar'],
    help:{ label:'Documentação da API da ZapSign', url:'https://docs.zapsign.com.br' },
  },
  clicksign: {
    color:'#1c9a6b',
    tagline:'Assinatura eletrônica com validade jurídica para propostas e contratos, sem sair da ficha do cliente.',
    paragraphs:[
      'Conecte a conta da Clicksign e envie o PDF da ficha para assinatura. O Horos cria o envelope, cadastra quem assina e a Clicksign manda o link por e-mail para cada pessoa.',
      'Quando o envelope é finalizado, o status muda na ficha. Se o aviso automático não estiver configurado, use o botão Atualizar para consultar a Clicksign.',
    ],
    listTitle:'Com a Clicksign no Horos você pode:',
    features:['Enviar PDF da ficha para assinatura','Várias pessoas no mesmo envelope','Lembrete automático a cada 3 dias','Ver o status e baixar o documento assinado'],
    slides:[
      { icon:'shield', title:'Validade jurídica', text:'Assinatura eletrônica' },
      { icon:'mail',   title:'Link por e-mail', text:'Lembrete automático' },
    ],
    setup:['Na Clicksign, abra Configurações › API e gere um token de acesso (API 3.0).','Cole o token abaixo e clique em Conectar.','Para o status mudar sozinho: em Configurações › Webhooks da Clicksign, cadastre o endereço que aparece abaixo, com os eventos de assinatura, finalização, recusa e cancelamento.','Na ficha do cliente, abra a aba Contratos e cobranças e clique em Enviar para assinatura.'],
    requires:['Conta na Clicksign com acesso à API','Cada pessoa que assina precisa de e-mail'],
    help:{ label:'Documentação da API da Clicksign', url:'https://developers.clicksign.com' },
  },
  asaas: {
    color:'#0030b9',
    tagline:'Cobre sinal, reserva e assessoria por Pix ou boleto pela ficha do cliente e veja na hora quando foi pago.',
    paragraphs:[
      'Conecte a conta Asaas da imobiliária. Na ficha do cliente, aba Contratos e cobranças, gere a cobrança com valor, vencimento e forma de pagamento: o Horos cria o cliente no Asaas (pelo CPF/CNPJ) e a cobrança.',
      'Você copia o Pix copia e cola, abre o boleto ou manda o link da fatura pelo WhatsApp do Horos. Quando o cliente paga, o status muda sozinho e chega um aviso no sino.',
    ],
    listTitle:'Com o Asaas no Horos você pode:',
    features:['Gerar Pix, boleto ou link que aceita os dois','Mandar a cobrança pelo WhatsApp do Horos','Ver quando foi paga, sem abrir o Asaas','Cancelar uma cobrança que não vale mais'],
    slides:[
      { icon:'banknote', title:'Pix e boleto', text:'Direto da ficha do cliente' },
      { icon:'bell',     title:'Aviso de pagamento', text:'Status muda sozinho' },
    ],
    setup:['No Asaas, abra Integrações › Chave de API e gere a chave.','Cole a chave abaixo, marque "Conta de testes" se for do sandbox, e clique em Conectar. O Horos confere a chave e cadastra o aviso de pagamento na sua conta.','Na ficha do cliente, abra a aba Contratos e cobranças e clique em Nova cobrança.'],
    requires:['Conta Asaas aprovada (para receber de verdade)','CPF ou CNPJ de quem paga'],
    help:{ label:'Documentação da API do Asaas', url:'https://docs.asaas.com' },
  },
  simulador: {
    color:'#1F93FF',
    tagline:'Simule parcelas no SAC e no Price e veja em qual faixa do Minha Casa Minha Vida o cliente entra.',
    paragraphs:[
      'Informe a renda, o valor do imóvel, a entrada e o FGTS: o Horos indica a faixa do Minha Casa Minha Vida pela renda, calcula a parcela, o total pago e quanto a renda permite financiar.',
      'O simulador avisa quando a parcela passa de 30% da renda, quando o financiamento passa de 80% do imóvel e quando o imóvel passa do teto da faixa. Na ficha do cliente, a simulação fica guardada e entra na pasta para o CCA.',
      'Taxas e tetos são valores de referência e mudam: é uma estimativa para conversar com o cliente, não uma proposta do banco.',
    ],
    listTitle:'Com o simulador você pode:',
    features:['Comparar SAC e Price','Ver a faixa do MCMV pela renda (Faixas 1 a 4 e SBPE)','Considerar entrada, FGTS e subsídio','Copiar a simulação para mandar ao cliente','Guardar na ficha e levar para a pasta do CCA'],
    slides:[
      { icon:'calculator', title:'SAC e Price', text:'Parcela, total e juros' },
      { icon:'banknote',   title:'Minha Casa Minha Vida', text:'Faixa pela renda' },
    ],
    setup:['Na ficha do cliente, abra a aba Crédito: o simulador já vem com a renda, o valor pretendido e a entrada da ficha.','Ajuste os valores e escolha SAC ou Price.','Clique em Guardar na ficha (e depois em Salvar) para a simulação ficar no cliente.','Para simular sem cliente, use o botão Abrir simulador aqui.'],
    requires:['Nada: já vem no Horos'],
    help:{ label:'Simulador habitacional da CAIXA', url:'https://www8.caixa.gov.br/siopiinternet-web/simulaOperacaoInternet.do?method=inicializarCasoUso' },
  },
};

const INT_STRIP = ['whatsapp','meta','gcal','zap','gads','zapsign','asaas'];
const INT_RECOMMENDED = ['whatsapp','meta','gcal','gmail','zap','vivareal','imovelweb','chaves','gads','zapier','asaas'];

function _intNorm(s) { return String(s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, ''); }

function IntStripTile({ app, onOpen }) {
  const styles = {
    whatsapp: { bg:'#25D366', fg:'#fff' }, meta: { bg:'#1877f2', fg:'#fff' }, gcal: { bg:'#e8f0fe', fg:'#1a73e8' },
    zap: { bg:'#e8541c', fg:'#fff' }, gads: { bg:'#fff', fg:'#1a73e8' }, asaas: { bg:'#0030b9', fg:'#fff' }, zapsign: { bg:'#3b5bdb', fg:'#fff' },
  }[app.id] || { bg:'var(--bg-2)', fg:'var(--fg)' };
  const word = { whatsapp:'WhatsApp', meta:'Facebook e Instagram', gcal:'Google Agenda', zap:'ZAP Imóveis', gads:'Google Ads', asaas:'Asaas', zapsign:'ZapSign' }[app.id] || app.name;
  return (
    <button className="int-strip-tile" style={{ background:styles.bg, color:styles.fg }} onClick={() => onOpen(app)} title={app.name}>
      {INT_LOGOS[app.id] && <IntLogo app={app} size={28} />}
      <span>{word}</span>
    </button>
  );
}

function IntStatus({ status }) {
  if (status === 'connected') return <span className="int-status int-status--on"><Icon name="check" size={13} /> Conectado</span>;
  if (status === 'soon')      return <span className="int-status int-status--soon">Planejado</span>;
  if (status === 'loading')   return <Skel w={74} h={18} r={9} />;
  return <span className="int-status">Disponível</span>;
}

// Selo do tipo de conexão — deixa claro o WhatsApp oficial (API do Meta) x não oficial (QR code)
function IntTag({ tag }) {
  if (tag === 'beta') return <span className="int-tag int-tag--beta" title="Já funciona, mas ainda não foi testada com uma conta real deste fornecedor">Beta</span>;
  return tag === 'oficial'
    ? <span className="int-tag int-tag--of" title="Conexão oficial pela API do Meta">Oficial</span>
    : <span className="int-tag int-tag--nof" title="Conexão pelo WhatsApp Web (QR code), sem aprovação do Meta">Não oficial</span>;
}

function IntAppCard({ app, status, onOpen }) {
  return (
    <button className={'int-app' + (status === 'soon' ? ' is-soon' : '') + (app.tag ? ' has-tag' : '')} onClick={() => onOpen(app)}>
      <div className="int-app-top">
        <IntLogo app={app} size={40} />
        <div style={{ minWidth:0 }}>
          <div className="int-app-name">{app.name}</div>
          {app.tag && <IntTag tag={app.tag} />}
          {app.beta && <IntTag tag="beta" />}
        </div>
      </div>
      <div className="int-app-desc">{app.desc}</div>
      <div className="int-app-foot"><IntStatus status={status} /><span className="int-app-by">{app.by}</span></div>
    </button>
  );
}

// ── Janela do aplicativo (layout do Marketplace) ──────────────────────────────
const INT_HOW = {
  properties: 'Como funciona: o Horos lê o estoque direto do seu sistema e atualiza sozinho todo dia. Imóvel que sai do sistema deixa de aparecer como disponível. A IA do Horos usa esse estoque para responder os clientes e sugerir opções parecidas com o que cada um procura.',
  api4com: 'Como funciona: a diretoria conecta a conta da Api4Com e cada pessoa informa o próprio ramal. No Horos 360 aparece o botão Ligar: o seu ramal toca, você atende e a Api4Com liga para o cliente. A duração e a gravação ficam registradas no lead.',
  gmail: 'Como funciona: cada pessoa conecta o próprio Gmail uma vez. No Horos 360 aparece o botão E-mail: você escreve e o e-mail sai da sua conta do Gmail, como se tivesse escrito lá, e fica registrado no lead. O Horos só pede permissão para enviar — não lê a sua caixa de entrada.',
  gads_conv: 'Como funciona: quando um lead que veio de anúncio do Google chega a uma etapa escolhida (por exemplo, Fechado), o Horos avisa o Google Ads — com o valor da venda. O Google passa a otimizar as campanhas pelos cliques que viram cliente, e não só pelos que viram lead.',
};
function _intDetailsOf(app) {
  if (INT_DETAILS[app.id]) return INT_DETAILS[app.id];
  if (INT_HOW[app.kind]) {
    const goals = (app.goals || []).map(g => INT_GOALS.find(x => x.id === g)?.label).filter(Boolean);
    return {
      color: app.bg || '#3a3f47', tagline: app.desc,
      paragraphs: [app.desc, INT_HOW[app.kind]],
      listTitle: 'Vai ajudar você a:', features: goals,
      slides: [{ icon: { api4com:'phone', gads_conv:'target', gmail:'msgs' }[app.kind] || 'home', title: app.name, text: app.desc }],
      setup: null, requires: null, help: null,
    };
  }
  const goals = (app.goals || []).map(g => INT_GOALS.find(x => x.id === g)?.label).filter(Boolean);
  return {
    color: app.bg || '#3a3f47',
    tagline: app.desc,
    paragraphs: [
      app.desc,
      'Esta integração está no roteiro do Horos. Clique em "Avise-me" e conte para a gente como você pretende usar.',
    ],
    listTitle: 'Vai ajudar você a:',
    features: goals,
    slides: [{ icon:'clock', title:'Planejado para o Horos', text:'Estamos preparando esta integração' }],
    setup: null,
    requires: null,
    help: null,
  };
}

function IntSlide({ app, slide, color, first }) {
  if (first) {
    return (
      <div className="int-slide" style={{ background:`radial-gradient(circle at 50% 55%, color-mix(in srgb, ${color} 55%, #fff) 0%, ${color} 70%)` }}>
        <div className="int-slide-pair">
          <div className="int-slide-orb"><img src="favicon.svg" alt="Horos" /></div>
          <div className="int-slide-sync"><Icon name="refresh" size={34} stroke={1.6} /></div>
          <div className="int-slide-orb"><IntLogo app={app} size={96} /></div>
        </div>
        <div className="int-slide-cap">Horos + {app.name}</div>
      </div>
    );
  }
  return (
    <div className="int-slide" style={{ background:`linear-gradient(135deg, ${color}, color-mix(in srgb, ${color} 60%, #000))` }}>
      <div className="int-slide-feat">
        <div className="int-slide-ic"><Icon name={slide.icon} size={44} stroke={1.6} /></div>
        <div className="int-slide-title">{slide.title}</div>
        <div className="int-slide-text">{slide.text}</div>
      </div>
    </div>
  );
}

function IntAppModal({ app, status, onClose, primary, connection, initialTab, disconnect, waChooser }) {
  const d = _intDetailsOf(app);
  const [tab, setTab]     = React.useState(initialTab || 'desc');
  const [askOff, setAskOff] = React.useState(false);   // confirmação do Desconectar
  const [busyOff, setBusyOff] = React.useState(false);
  const runOff = async () => {
    setBusyOff(true);
    try { await disconnect.run(); setAskOff(false); } finally { setBusyOff(false); }
  };
  const [slide, setSlide] = React.useState(0);
  const [choosing, setChoosing] = React.useState(false);   // WhatsApp: escolher oficial x não oficial
  const slides = [{ hero:true }, ...(d.slides || [])];
  const go = (n) => setSlide((n + slides.length) % slides.length);
  const goals = (app.goals || []).map(g => INT_GOALS.find(x => x.id === g)?.label).filter(Boolean);
  const badge = status === 'connected' ? { t:'Conectado', c:'on' } : status === 'soon' ? { t:'Planejado', c:'soon' } : { t:'Disponível', c:'av' };

  return (
    <div className="int-modal-ov" onClick={onClose}>
      <div className="int-modal" role="dialog" aria-label={app.name} onClick={e => e.stopPropagation()}>
        <div className="int-modal-head">
          <div className="int-modal-title">{app.name}</div>
          <button className="int-banner-x" onClick={onClose} title="Fechar (Esc)"><Icon name="close" size={18} /></button>
        </div>

        <div className="int-modal-body">
          <div className="int-modal-app">
            <IntLogo app={app} size={80} />
            <div style={{ minWidth:0 }}>
              <div className="int-modal-name">{app.name} <span className={'int-badge int-badge--' + badge.c}>{badge.t}</span>{app.tag && <IntTag tag={app.tag} />}{app.beta && <IntTag tag="beta" />}</div>
              {goals.length > 0 && <div className="int-modal-goals">{goals.join(' · ')}</div>}
              <div className="int-modal-by">{app.by}</div>
              {app.beta && <div className="int-beta-note">Integração nova: já funciona, mas ainda não foi testada com uma conta real do {app.name}. Se algum dado não chegar certo, fale com o suporte que a gente ajusta.</div>}
            </div>
          </div>

          {choosing ? <IntWaChooser {...(waChooser || {})} onDone={() => setChoosing(false)} /> : (<>
          <div className="int-modal-tabs">
            {[['desc','Descrição'],['setup','Instalação'],['res','Recursos']].map(([id, l]) => (
              <button key={id} className={tab === id ? 'on' : ''} onClick={() => setTab(id)}>{l}</button>
            ))}
          </div>

          {tab === 'desc' && (
            <>
              <div className="int-carousel">
                {slides.length > 1 && <button className="int-car-arrow" onClick={() => go(slide - 1)} title="Anterior"><Icon name="arrLeft" size={20} /></button>}
                <div className="int-car-frame">
                  <IntSlide app={app} slide={slides[slide]} color={d.color} first={slide === 0} />
                </div>
                {slides.length > 1 && <button className="int-car-arrow" onClick={() => go(slide + 1)} title="Próximo"><Icon name="arrRight" size={20} /></button>}
              </div>
              {slides.length > 1 && (
                <div className="int-car-dots">
                  {slides.map((_, i) => <button key={i} className={i === slide ? 'on' : ''} onClick={() => setSlide(i)} aria-label={`Slide ${i + 1}`} />)}
                </div>
              )}
              <p className="int-modal-tagline">{d.tagline}</p>
              <hr className="int-modal-hr" />
              {d.notice && <div className={'int-notice int-notice--' + d.notice.kind}><b>{d.notice.title}</b> {d.notice.text}</div>}
              <div className="int-modal-text">
                {d.paragraphs.map((p, i) => <p key={i}>{p}</p>)}
                {d.features && d.features.length > 0 && (
                  <>
                    <p>{d.listTitle}</p>
                    <ul className="int-modal-checks">{d.features.map(f => <li key={f}>{f}</li>)}</ul>
                  </>
                )}
              </div>
            </>
          )}

          {tab === 'setup' && (
            <div className="int-modal-text">
              {d.notice && <div className={'int-notice int-notice--' + d.notice.kind}><b>{d.notice.title}</b> {d.notice.text}</div>}
              {connection}
              {d.setup ? (
                <>
                  <h3>Como instalar</h3>
                  <ol className="int-modal-steps">{d.setup.map(s => <li key={s}>{s}</li>)}</ol>
                  {d.requires && (
                    <>
                      <h3>O que você precisa</h3>
                      <ul className="int-modal-checks">{d.requires.map(s => <li key={s}>{s}</li>)}</ul>
                    </>
                  )}
                </>
              ) : (app.kind === 'soon' || !connection) && (
                <>
                  <h3>Ainda não disponível</h3>
                  <p>Esta integração está em desenvolvimento. Assim que ela chegar, as instruções de instalação aparecem aqui.</p>
                  {d.requires && (
                    <>
                      <h3>O que você vai precisar</h3>
                      <ul className="int-modal-checks">{d.requires.map(s => <li key={s}>{s}</li>)}</ul>
                    </>
                  )}
                </>
              )}
            </div>
          )}

          {tab === 'res' && (
            <div className="int-modal-text">
              <h3>Links úteis</h3>
              <div className="int-res">
                {d.help && <a href={d.help.url} target="_blank" rel="noopener noreferrer"><Icon name="extLink" size={15} /> {d.help.label}</a>}
                <a href="about-integrations.html"><Icon name="info" size={15} /> Sobre as integrações do Horos</a>
                <a href="privacy.html" target="_blank" rel="noopener"><Icon name="shield" size={15} /> Política de Privacidade</a>
                <a href="terms.html" target="_blank" rel="noopener"><Icon name="file" size={15} /> Termos de Serviço</a>
                <a href={`support.html?contato=1&assunto=integracoes&page=integrations.html&titulo=${encodeURIComponent('Integração ' + app.name)}`}><Icon name="mail" size={15} /> Falar com o suporte</a>
              </div>
            </div>
          )}
          </>)}
        </div>

        {askOff && disconnect ? (
        <div className="int-modal-foot int-modal-foot--confirm">
          <div className="int-off-ask"><b>Desconectar {disconnect.what}?</b> {disconnect.note}</div>
          <button className="int-btn" onClick={() => setAskOff(false)} disabled={busyOff}>Cancelar</button>
          <button className="int-btn int-btn--danger" onClick={runOff} disabled={busyOff}>{busyOff ? 'Desconectando…' : 'Desconectar'}</button>
        </div>
        ) : (
        <div className="int-modal-foot">
          {disconnect && !choosing && (
            <button className="int-btn int-btn--off" onClick={() => setAskOff(true)}><Icon name="power" size={15} /> Desconectar</button>
          )}
          <span style={{ flex:1 }} />
          {choosing
            ? <button className="int-btn" onClick={() => setChoosing(false)}><Icon name="arrLeft" size={15} /> Voltar</button>
            : <button className="int-btn" onClick={onClose}>Fechar</button>}
          {!choosing && primary && (primary.choose
            ? <button className="int-btn int-btn--primary" onClick={() => setChoosing(true)}>{primary.label}</button>
            : primary.toTab
            ? (tab !== primary.toTab && <button className="int-btn int-btn--primary" onClick={() => setTab(primary.toTab)}>{primary.label}</button>)
            : primary.href
            ? <a className="int-btn int-btn--primary" href={primary.href}>{primary.label}</a>
            : <button className="int-btn int-btn--primary" onClick={primary.onClick} disabled={primary.disabled}>{primary.label}</button>)}
        </div>
        )}
      </div>
    </div>
  );
}

// WhatsApp — escolha da forma de conectar (oficial x não oficial)
// API oficial: cadastro incorporado do Meta (Embedded Signup — janela do Facebook) ou, se o app ainda não
// tiver a configuração, conexão manual com o token permanente do Gerenciador de Negócios.
let _fbSdkPromise = null;
function intLoadFbSdk(appId, version) {
  if (window.FB) return Promise.resolve(window.FB);
  if (_fbSdkPromise) return _fbSdkPromise;
  _fbSdkPromise = new Promise((resolve, reject) => {
    window.fbAsyncInit = () => {
      window.FB.init({ appId, autoLogAppEvents: true, xfbml: false, version: version || 'v25.0' });
      resolve(window.FB);
    };
    const sc = document.createElement('script');
    sc.src = 'https://connect.facebook.net/pt_BR/sdk.js';
    sc.async = true; sc.defer = true; sc.crossOrigin = 'anonymous';
    sc.onerror = () => { _fbSdkPromise = null; reject(new Error('sdk')); };
    document.body.appendChild(sc);
  });
  return _fbSdkPromise;
}

async function intAuthFetch(url, opts = {}) {
  const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
  const r = await fetch(url, { ...opts, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${tok}`, ...(opts.headers || {}) } });
  const d = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(d.error || 'Não foi possível concluir. Tente de novo.');
  return d;
}

function IntWaOfficial({ waStatus, waProvider, onConnected }) {
  const [cfg, setCfg]     = React.useState(null);      // { app_id, config_id, graph }
  const [phase, setPhase] = React.useState('idle');    // idle | meta | saving | error
  const [err, setErr]     = React.useState('');
  const [manual, setManual] = React.useState(false);
  const [f, setF] = React.useState({ phone_number_id: '', waba_id: '', access_token: '', pin: '' });
  const pending = React.useRef({ code: null, info: null });
  const switching = waStatus === 'connected' && waProvider !== 'cloud';

  React.useEffect(() => {
    fetch('/api/whatsapp-cloud?config').then(r => r.json()).then(c => {
      setCfg(c);
      if (c.app_id && c.config_id) intLoadFbSdk(c.app_id, c.graph).catch(() => {});
      if (!c.config_id) setManual(true);
    }).catch(() => { setCfg({}); setManual(true); });
  }, []);

  // O código do Meta vale 30 s: assim que chegarem o código e os dados do número, conecta
  const finish = async () => {
    const { code, info } = pending.current;
    if (!code || !info) return;
    pending.current = { code: null, info: null };
    setPhase('saving');
    try {
      const d = await intAuthFetch('/api/whatsapp-cloud', { method: 'POST', body: JSON.stringify({ action: 'connect', code, ...info }) });
      onConnected && onConnected(d);
    } catch (e) { setErr(e.message); setPhase('error'); }
  };
  const finishRef = React.useRef(finish);
  finishRef.current = finish;

  // A janela do Meta avisa por postMessage qual conta e número foram escolhidos
  React.useEffect(() => {
    const onMsg = (ev) => {
      let host = '';
      try { host = new URL(ev.origin).hostname; } catch { return; }
      if (!/(^|\.)facebook\.com$/.test(host)) return;
      let data; try { data = typeof ev.data === 'string' ? JSON.parse(ev.data) : ev.data; } catch { return; }
      if (data?.type !== 'WA_EMBEDDED_SIGNUP') return;
      if (data.event === 'CANCEL') { setPhase('idle'); return; }
      if (data.event === 'ERROR') { setErr(data.data?.error_message || 'O Meta não concluiu o cadastro.'); setPhase('error'); return; }
      if (String(data.event || '').startsWith('FINISH') && data.data?.phone_number_id) {
        pending.current.info = { phone_number_id: data.data.phone_number_id, waba_id: data.data.waba_id, business_id: data.data.business_id };
        finishRef.current();
      }
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, []);

  const startMeta = () => {
    setErr('');
    if (!window.FB) { setErr('A janela do Facebook ainda está carregando. Tente de novo em alguns segundos.'); return; }
    setPhase('meta');
    window.FB.login((resp) => {
      const code = resp?.authResponse?.code;
      if (!code) { setPhase(p => (p === 'saving' ? p : 'idle')); return; }
      pending.current.code = code;
      finishRef.current();
    }, {
      config_id: cfg.config_id, response_type: 'code', override_default_response_type: true,
      extras: { setup: {}, sessionInfoVersion: '3' },
    });
  };

  const saveManual = async (e) => {
    e.preventDefault();
    setErr(''); setPhase('saving');
    try {
      const d = await intAuthFetch('/api/whatsapp-cloud', { method: 'POST', body: JSON.stringify({ action: 'manual', ...f }) });
      onConnected && onConnected(d);
    } catch (e2) { setErr(e2.message); setPhase('error'); }
  };

  const busy = phase === 'meta' || phase === 'saving';
  const set = k => e => setF(p => ({ ...p, [k]: e.target.value }));
  return (
    <div className="int-wa-off">
      {switching && <div className="int-notice int-notice--warn"><b>Seu WhatsApp pelo QR code será desconectado.</b> Cada pessoa usa um WhatsApp no Horos. As conversas que já estão aqui continuam salvas.</div>}
      {!manual && (
        <button className="int-btn int-btn--primary int-wa-meta-btn" onClick={startMeta} disabled={busy || !cfg}>
          {phase === 'meta' ? 'Continue na janela do Facebook…' : phase === 'saving' ? 'Conectando o número…' : 'Conectar com a Meta'}
        </button>
      )}
      {manual && (
        <form className="int-wa-form" onSubmit={saveManual}>
          {!cfg?.config_id && <div className="int-muted">Use os dados do WhatsApp Manager (Gerenciador de Negócios › Contas do WhatsApp › Configuração da API).</div>}
          <label>ID do número de telefone<input value={f.phone_number_id} onChange={set('phone_number_id')} inputMode="numeric" placeholder="Ex.: 123456789012345" required /></label>
          <label>ID da conta do WhatsApp Business (WABA)<input value={f.waba_id} onChange={set('waba_id')} inputMode="numeric" placeholder="Ex.: 102938475610293" required /></label>
          <label>Token de acesso permanente<input value={f.access_token} onChange={set('access_token')} type="password" autoComplete="off" placeholder="Token do usuário do sistema" required /></label>
          <label>PIN de 6 dígitos <span className="int-muted">(opcional — só se o número ainda não foi registrado)</span><input value={f.pin} onChange={set('pin')} inputMode="numeric" maxLength={6} placeholder="123456" /></label>
          <button className="int-btn int-btn--primary" type="submit" disabled={busy}>{phase === 'saving' ? 'Conectando…' : 'Conectar número'}</button>
        </form>
      )}
      {err && <div className="int-wa-err">{err}</div>}
      {cfg?.config_id && (
        <button type="button" className="int-link-btn" onClick={() => { setManual(m => !m); setErr(''); setPhase('idle'); }}>
          {manual ? 'Voltar para "Conectar com a Meta"' : 'Já tenho o token: conectar manualmente'}
        </button>
      )}
    </div>
  );
}

function IntWaChooser({ waStatus, waProvider, onCloudConnected, onDone }) {
  const [official, setOfficial] = React.useState(false);
  const cloudOn = waProvider === 'cloud' && waStatus === 'connected';
  return (
    <div className="int-wa-choose">
      <h3 className="int-wa-choose-title">Como você quer conectar o WhatsApp?</h3>
      <p className="int-wa-choose-sub">Existem duas formas. A oficial é a indicada para imobiliárias e disparos; o QR code conecta em um minuto com o número do seu celular.</p>
      <div className="int-wa-opts">
        <div className="int-wa-opt">
          <div className="int-wa-opt-head">
            <div className="int-wa-opt-ic" style={{ background:'#075e54' }}><svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><path d={WA_PATH}/></svg></div>
            <div>
              <div className="int-wa-opt-name">API oficial do Meta <IntTag tag="oficial" /></div>
              <div className="int-wa-opt-by">WhatsApp Business Platform</div>
            </div>
          </div>
          <p>Conexão direta com o Meta, com o número verificado da imobiliária. Mais estável e indicada para disparos.</p>
          <ul className="int-modal-checks">
            <li>Não depende do celular ligado</li>
            <li>Modelos de mensagem aprovados pelo Meta</li>
            <li>Sem risco de bloqueio por conexão via QR code</li>
            <li className="is-warn">Passadas 24 h da última mensagem do cliente, só sai modelo aprovado (cobrado pelo Meta)</li>
          </ul>
          {official
            ? <IntWaOfficial waStatus={waStatus} waProvider={waProvider} onConnected={(d) => { onCloudConnected && onCloudConnected(d); onDone && onDone(); }} />
            : (
            <div className="int-wa-opt-foot">
              <span className="int-badge int-badge--av">{cloudOn ? 'Conectado' : 'Disponível'}</span>
              <button className="int-btn int-btn--primary" onClick={() => setOfficial(true)}>{cloudOn ? 'Trocar número' : 'Conectar pela API oficial'}</button>
            </div>
          )}
        </div>
        <div className="int-wa-opt">
          <div className="int-wa-opt-head">
            <div className="int-wa-opt-ic" style={{ background:'#25D366' }}><svg width="22" height="22" viewBox="0 0 24 24" fill="#fff"><path d={WA_PATH}/></svg></div>
            <div>
              <div className="int-wa-opt-name">QR code <IntTag tag="nao-oficial" /></div>
              <div className="int-wa-opt-by">WhatsApp Web do seu celular</div>
            </div>
          </div>
          <p>Usa o WhatsApp do seu celular, sem aprovação do Meta. Conecta em um minuto com qualquer número.</p>
          <ul className="int-modal-checks">
            <li>Funciona com WhatsApp e WhatsApp Business</li>
            <li>Sem custo por mensagem</li>
            <li>Conversas e etiquetas iguais às do celular</li>
            <li className="is-warn">O Meta pode bloquear números com muitos envios em massa</li>
          </ul>
          <div className="int-wa-opt-foot">
            <span className="int-badge int-badge--av">Disponível</span>
            {cloudOn
              ? <span className="int-muted" style={{ fontSize: 12.5, textAlign: 'right' }}>Desconecte a API oficial para usar o QR code</span>
              : <a className="int-btn int-btn--primary" href="messages.html">Conectar pelo QR code</a>}
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Página principal ──────────────────────────────────────────────────────────
// ── Integrações abertas (API, webhooks, Zapier/Make, Google Ads, Conversões do Meta) ─────────────
// Tudo passa por /api/integrations (token do usuário). A chave e o segredo aparecem uma vez só.
const INT_EVENT_LABELS = {
  'lead.created': 'Lead novo', 'lead.stage_changed': 'Mudou de etapa', 'lead.won': 'Venda (Fechado)', 'lead.archived': 'Lead arquivado',
};
const INT_API_BASE = 'https://horos.sale/api/v1';

async function intOpenCall(body) {
  const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
  const r = await fetch('/api/integrations', {
    method: body ? 'POST' : 'GET', cache: 'no-store',
    headers: { Authorization: `Bearer ${tok}`, ...(body ? { 'Content-Type': 'application/json' } : {}) },
    body: body ? JSON.stringify(body) : undefined,
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw new Error(j.error || 'Não foi possível concluir. Tente de novo.');
  return j;
}

function _intAgo(ts) {
  if (!ts) return 'nunca';
  const m = Math.round((Date.now() - new Date(ts).getTime()) / 60000);
  if (m < 1) return 'agora';
  if (m < 60) return `há ${m} min`;
  if (m < 1440) return `há ${Math.round(m / 60)} h`;
  return new Date(ts).toLocaleDateString('pt-BR');
}

function IntCopy({ text, label }) {
  const [ok, setOk] = React.useState(false);
  return (
    <div className="int-op-copy">
      <code>{text}</code>
      <button className="int-btn" onClick={() => { navigator.clipboard?.writeText(text); setOk(true); setTimeout(() => setOk(false), 1500); }}>
        <Icon name={ok ? 'check' : 'copy'} size={14} /> {ok ? 'Copiado' : (label || 'Copiar')}
      </button>
    </div>
  );
}

function IntScopePick({ st, value, onChange }) {
  if (!st?.can_workspace) return null;
  return (
    <label className="int-op-field">
      <span>Vale para</span>
      <select value={value} onChange={e => onChange(e.target.value)}>
        <option value="me">Só os meus leads</option>
        <option value="workspace">A imobiliária toda</option>
      </select>
    </label>
  );
}

function IntKeysBlock({ st, kinds, defaultKind, title, onState, showToast, createLabel }) {
  const [name, setName] = React.useState('');
  const [scope, setScope] = React.useState('me');
  const [busy, setBusy] = React.useState(false);
  const [fresh, setFresh] = React.useState(null);
  const [revoking, setRevoking] = React.useState(null);
  const keys = (st?.keys || []).filter(k => kinds.includes(k.kind));
  const create = async () => {
    setBusy(true);
    try { const j = await intOpenCall({ action:'create_key', name, kind: defaultKind, scope }); setFresh(j.key); setName(''); onState(j); }
    catch (e) { showToast(e.message); }
    setBusy(false);
  };
  const revoke = async (k) => {
    try { onState(await intOpenCall({ action:'revoke_key', id: k.id })); showToast('Chave revogada.'); }
    catch (e) { showToast(e.message); }
    setRevoking(null);
  };
  return (
    <div className="int-block">
      <div className="int-block-title">{title} <span className="int-count">{keys.length}</span></div>
      {fresh && (
        <div className="int-op-fresh">
          <b>Copie a chave agora.</b> Por segurança, ela não aparece de novo.
          <IntCopy text={fresh} />
        </div>
      )}
      {keys.length === 0 && !fresh && <div className="int-muted">Nenhuma chave ainda.</div>}
      {keys.map(k => (
        <div key={k.id} className="int-conn-row">
          <div className="int-conn-av"><Icon name="key" size={15} /></div>
          <div style={{ flex:1, minWidth:0 }}>
            <div className="int-conn-name">{k.name} <span className="int-muted">· {k.key_prefix}…</span></div>
            <div className="int-muted">
              {k.scope === 'workspace' ? 'Imobiliária toda' : 'Só seus leads'} · usada {_intAgo(k.last_used_at)}{k.leads_count ? ` · ${k.leads_count} lead${k.leads_count > 1 ? 's' : ''} criado${k.leads_count > 1 ? 's' : ''}` : ''}
            </div>
          </div>
          {revoking === k.id ? (
            <>
              <button className="int-btn" onClick={() => setRevoking(null)}>Cancelar</button>
              <button className="int-btn int-btn--danger" onClick={() => revoke(k)}>Revogar</button>
            </>
          ) : <button className="int-btn" onClick={() => setRevoking(k.id)}>Revogar</button>}
        </div>
      ))}
      <div className="int-op-form">
        <label className="int-op-field">
          <span>Nome da chave</span>
          <input value={name} onChange={e => setName(e.target.value)} placeholder="Ex.: Site da imobiliária" maxLength={80} />
        </label>
        <IntScopePick st={st} value={scope} onChange={setScope} />
        <button className="int-btn int-btn--primary" onClick={create} disabled={busy}>{busy ? 'Gerando…' : (createLabel || 'Gerar chave')}</button>
      </div>
    </div>
  );
}

function IntHooksBlock({ st, onState, showToast, hint, source }) {
  const [form, setForm] = React.useState({ name:'', url:'', events:['lead.created'], scope:'me' });
  const [busy, setBusy] = React.useState(false);
  const [secret, setSecret] = React.useState(null);
  const [rowBusy, setRowBusy] = React.useState(null);
  const [delAsk, setDelAsk] = React.useState(null);
  const hooks = (st?.webhooks || []).filter(h => !source || h.source === source);
  const toggleEv = (e) => setForm(f => ({ ...f, events: f.events.includes(e) ? f.events.filter(x => x !== e) : [...f.events, e] }));
  const create = async () => {
    setBusy(true);
    try { const j = await intOpenCall({ action:'create_webhook', ...form, source: source || 'manual' }); setSecret(j.secret); setForm({ name:'', url:'', events:['lead.created'], scope:'me' }); onState(j); }
    catch (e) { showToast(e.message); }
    setBusy(false);
  };
  const act = async (h, action, extra) => {
    setRowBusy(h.id + action);
    try {
      const j = await intOpenCall({ action, id: h.id, ...(extra || {}) });
      if (action === 'test_webhook') showToast(j.test?.ok ? 'Teste entregue.' : `Teste falhou: ${j.test?.error || 'sem resposta'}`);
      if (action === 'reveal_secret') { setSecret(j.secret); } else onState(j);
    } catch (e) { showToast(e.message); }
    setRowBusy(null); setDelAsk(null);
  };
  const hookName = (id) => hooks.find(h => h.id === id)?.name || 'Webhook';
  const statusTxt = { done:'Entregue', pending:'Tentando de novo', running:'Enviando', failed:'Falhou', skipped:'Ignorado' };
  return (
    <div className="int-block">
      <div className="int-block-title">Webhooks <span className="int-count">{hooks.length}</span></div>
      {hint && <div className="int-muted" style={{ marginBottom: 8 }}>{hint}</div>}
      {secret && (
        <div className="int-op-fresh">
          <b>Segredo do webhook.</b> Use para conferir a assinatura <code>X-Horos-Signature</code> (HMAC SHA-256 do corpo).
          <IntCopy text={secret} />
        </div>
      )}
      {hooks.length === 0 && <div className="int-muted">Nenhum webhook ainda.</div>}
      {hooks.map(h => (
        <div key={h.id} className={'int-conn-row' + (h.active ? '' : ' is-off')}>
          <div className="int-conn-av"><Icon name="link" size={15} /></div>
          <div style={{ flex:1, minWidth:0 }}>
            <div className="int-conn-name">{h.name || 'Webhook'} {h.source !== 'manual' && <span className="int-muted">· via {h.source === 'zapier' ? 'Zapier' : h.source === 'make' ? 'Make' : 'API'}</span>}</div>
            <div className="int-muted int-op-url">{h.url}</div>
            <div className="int-muted">
              {h.events.map(e => INT_EVENT_LABELS[e] || e).join(' · ')}
              {' · '}{!h.active ? <span className="int-op-bad">Desligado</span>
                : h.last_error ? <span className="int-op-bad">Última entrega falhou ({_intAgo(h.last_at)})</span>
                : h.last_at ? <span className="int-op-good">Funcionando · {_intAgo(h.last_at)}</span> : 'Ainda sem entregas'}
            </div>
          </div>
          {delAsk === h.id ? (
            <>
              <button className="int-btn" onClick={() => setDelAsk(null)}>Cancelar</button>
              <button className="int-btn int-btn--danger" onClick={() => act(h, 'delete_webhook')} disabled={!!rowBusy}>Excluir</button>
            </>
          ) : (
            <div className="int-op-actions">
              <button className="int-btn" onClick={() => act(h, 'test_webhook')} disabled={!!rowBusy || !h.active}>{rowBusy === h.id + 'test_webhook' ? 'Enviando…' : 'Testar'}</button>
              <button className="int-btn" onClick={() => act(h, 'toggle_webhook', { active: !h.active })} disabled={!!rowBusy}>{h.active ? 'Desligar' : 'Ligar'}</button>
              <button className="int-btn" onClick={() => act(h, 'reveal_secret')} disabled={!!rowBusy} title="Ver o segredo"><Icon name="key" size={14} /></button>
              <button className="int-btn" onClick={() => setDelAsk(h.id)} title="Excluir"><Icon name="trash" size={14} /></button>
            </div>
          )}
        </div>
      ))}
      <div className="int-op-form int-op-form--col">
        <div className="int-op-row">
          <label className="int-op-field">
            <span>Nome</span>
            <input value={form.name} onChange={e => setForm(f => ({ ...f, name: e.target.value }))} placeholder="Ex.: Zapier · planilha de vendas" maxLength={80} />
          </label>
          <IntScopePick st={st} value={form.scope} onChange={v => setForm(f => ({ ...f, scope: v }))} />
        </div>
        <label className="int-op-field">
          <span>Endereço (https)</span>
          <input value={form.url} onChange={e => setForm(f => ({ ...f, url: e.target.value }))} placeholder="https://hooks.zapier.com/hooks/catch/…" />
        </label>
        <div className="int-op-field">
          <span>Avisar quando</span>
          <div className="int-op-chips">
            {(st?.events || Object.keys(INT_EVENT_LABELS)).map(e => (
              <button key={e} type="button" className={'int-op-chip' + (form.events.includes(e) ? ' on' : '')} onClick={() => toggleEv(e)}>
                {form.events.includes(e) && <Icon name="check" size={12} />} {INT_EVENT_LABELS[e] || e}
              </button>
            ))}
          </div>
        </div>
        <div><button className="int-btn int-btn--primary" onClick={create} disabled={busy || !form.url || !form.events.length}>{busy ? 'Salvando…' : 'Adicionar webhook'}</button></div>
      </div>
      {(st?.deliveries || []).length > 0 && (
        <details className="int-op-log">
          <summary>Últimas entregas</summary>
          {(st.deliveries || []).map(d => (
            <div key={d.id} className="int-op-log-row">
              <span className={'int-op-dot ' + (d.status === 'done' ? 'ok' : d.status === 'failed' ? 'bad' : d.status === 'skipped' ? '' : 'wait')} />
              <span>{INT_EVENT_LABELS[d.event] || d.event}</span>
              <span className="int-muted">{hookName(d.webhook_id)}</span>
              <span className="int-muted">{statusTxt[d.status] || d.status}{d.attempts > 1 ? ` · ${d.attempts} tentativas` : ''}</span>
              <span className="int-muted" style={{ marginLeft:'auto' }}>{_intAgo(d.created_at)}</span>
              {d.last_error && d.status !== 'done' && <div className="int-op-err">{d.last_error}</div>}
            </div>
          ))}
        </details>
      )}
    </div>
  );
}

function IntApiDocs() {
  const ex = `curl -X POST ${INT_API_BASE}/leads \\
  -H "Authorization: Bearer hrs_SUA_CHAVE" \\
  -H "Content-Type: application/json" \\
  -d '{"name":"Maria Souza","phone":"11999998888","email":"maria@email.com","region":"Centro","value":350000,"temperature":"hot","notes":"Veio do site"}'`;
  const hook = `{
  "id": "1234",
  "event": "lead.stage_changed",
  "created_at": "2026-10-06T14:00:00Z",
  "data": {
    "lead": { "id": "…", "name": "Maria Souza", "phone": "11999998888", "stage": { "id": "em_analise", "name": "Em Análise" }, … },
    "previous_stage": { "id": "aguardando_docs", "name": "Aguardando Documentação" }
  }
}`;
  return (
    <div className="int-block">
      <div className="int-block-title">Referência rápida</div>
      <a className="int-link" href="developers.html" target="_blank" rel="noopener noreferrer" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, marginBottom: 10 }}>
        Documentação completa (exemplos, webhooks e assinatura) <Icon name="extLink" size={12} />
      </a>
      <div className="int-muted" style={{ marginBottom: 6 }}>Endereço base</div>
      <IntCopy text={INT_API_BASE} />
      <table className="int-op-table">
        <tbody>
          <tr><td>GET</td><td>/me</td><td>Testa a chave</td></tr>
          <tr><td>GET</td><td>/stages</td><td>Etapas do funil</td></tr>
          <tr><td>GET</td><td>/leads</td><td>Lista: ?created_since= ?updated_since= ?stage= ?phone= ?email= ?limit= (até 100) ?offset=</td></tr>
          <tr><td>GET</td><td>/leads/:id</td><td>Um lead</td></tr>
          <tr><td>POST</td><td>/leads</td><td>Cria: name, phone, email, region, value, temperature (hot/warm/cold), stage, notes, origin, utm, custom_fields</td></tr>
          <tr><td>PATCH</td><td>/leads/:id</td><td>Altera: stage, temperature, value, name, email, region, notes, custom_fields</td></tr>
          <tr><td>POST</td><td>/hooks</td><td>Assina eventos: url, events (REST hooks do Zapier/Make)</td></tr>
          <tr><td>DELETE</td><td>/hooks/:id</td><td>Cancela a assinatura</td></tr>
        </tbody>
      </table>
      <div className="int-muted" style={{ margin: '10px 0 6px' }}>Exemplo: criar lead</div>
      <pre className="int-op-pre">{ex}</pre>
      <div className="int-muted" style={{ margin: '10px 0 6px' }}>Respostas: 201 criado · 200 reativado (estava arquivado) · 409 telefone já existe · 401 chave inválida · 402 assinatura inativa</div>
      <div className="int-muted" style={{ margin: '10px 0 6px' }}>Corpo de um webhook</div>
      <pre className="int-op-pre">{hook}</pre>
    </div>
  );
}

const INT_CAPI_DEFAULT = { em_conversa:'Contato', aguardando_docs:'Documentação', em_analise:'Análise de crédito', agendado:'Visita agendada', aprovado_final:'Crédito aprovado', fechado:'Purchase' };

function IntCapiBlock({ st, onState, showToast }) {
  const c = st?.capi;
  const stages = (st?.stages || []).filter(s => !/^arquiv/i.test(s.name) && s.id !== 'inbox');
  const [f, setF] = React.useState(() => ({
    dataset_id: c?.dataset_id || '', token: '',
    stages: c?.stages || Object.fromEntries(stages.filter(s => INT_CAPI_DEFAULT[s.id]).map(s => [s.id, s.id === 'fechado' ? 'Purchase' : s.name])),
    send_created: c ? c.send_created !== false : true, only_meta: c ? c.only_meta !== false : true,
    enabled: c ? c.enabled !== false : true, test_event_code: c?.test_event_code || '',
  }));
  const [busy, setBusy] = React.useState(null);
  const [delAsk, setDelAsk] = React.useState(false);
  if (!st?.can_workspace) {
    return <div className="int-block"><div className="int-muted">Só a diretoria (ou quem criou a imobiliária) configura as Conversões do Meta.</div></div>;
  }
  const setStage = (id, v) => setF(x => { const s = { ...x.stages }; if (v === false) delete s[id]; else s[id] = v; return { ...x, stages: s }; });
  const save = async () => {
    setBusy('save');
    try { onState(await intOpenCall({ action:'save_capi', ...f })); setF(x => ({ ...x, token:'' })); showToast('Conversões do Meta salvas.'); }
    catch (e) { showToast(e.message); }
    setBusy(null);
  };
  const test = async () => {
    setBusy('test');
    try { const j = await intOpenCall({ action:'test_capi', test_event_code: f.test_event_code }); showToast(`O Meta recebeu ${j.received || 1} evento de teste. Confira no Gerenciador de Eventos.`); }
    catch (e) { showToast(e.message); }
    setBusy(null);
  };
  const del = async () => {
    setBusy('del');
    try { onState(await intOpenCall({ action:'delete_capi' })); showToast('Conversões do Meta removidas.'); }
    catch (e) { showToast(e.message); }
    setBusy(null); setDelAsk(false);
  };
  return (
    <div className="int-block">
      <div className="int-block-title">Conjunto de dados</div>
      {c && (
        <div className="int-muted" style={{ marginBottom: 10 }}>
          {c.enabled ? <span className="int-op-good">Ligado</span> : <span className="int-op-bad">Desligado</span>}
          {c.dataset_name ? <> · {c.dataset_name}</> : null}
          {c.last_at ? <> · último envio {_intAgo(c.last_at)}{c.last_event ? ` (${c.last_event})` : ''}</> : ' · nenhum evento enviado ainda'}
          {c.last_error && <div className="int-op-err">Último erro: {c.last_error}</div>}
        </div>
      )}
      <div className="int-op-form int-op-form--col">
        <div className="int-op-row">
          <label className="int-op-field">
            <span>ID do conjunto de dados (pixel)</span>
            <input value={f.dataset_id} onChange={e => setF(x => ({ ...x, dataset_id: e.target.value.replace(/\D/g, '') }))} placeholder="123456789012345" inputMode="numeric" />
          </label>
          <label className="int-op-field">
            <span>Token de acesso {c?.has_token && <em className="int-muted">(salvo — deixe vazio para manter)</em>}</span>
            <input type="password" value={f.token} onChange={e => setF(x => ({ ...x, token: e.target.value }))} placeholder={c?.has_token ? '••••••••' : 'EAAB…'} autoComplete="off" />
          </label>
        </div>
        <div className="int-op-field">
          <span>Etapas que viram evento (nome do evento ao lado)</span>
          <div className="int-op-stages">
            <label className="int-op-stage">
              <input type="checkbox" checked={f.send_created} onChange={e => setF(x => ({ ...x, send_created: e.target.checked }))} />
              <span>Lead entrou no Horos</span>
              <input value="Lead" disabled />
            </label>
            {stages.map(s => (
              <label key={s.id} className="int-op-stage">
                <input type="checkbox" checked={!!f.stages[s.id]} onChange={e => setStage(s.id, e.target.checked ? (s.id === 'fechado' ? 'Purchase' : s.name) : false)} />
                <span>{s.name}</span>
                <input value={typeof f.stages[s.id] === 'string' ? f.stages[s.id] : ''} disabled={!f.stages[s.id]}
                  onChange={e => setStage(s.id, e.target.value || s.name)} maxLength={50} />
              </label>
            ))}
          </div>
        </div>
        <label className="int-op-check">
          <input type="checkbox" checked={f.only_meta} onChange={e => setF(x => ({ ...x, only_meta: e.target.checked }))} />
          <span>Só leads que vieram do Meta Ads <em className="int-muted">(recomendado)</em></span>
        </label>
        <label className="int-op-check">
          <input type="checkbox" checked={f.enabled} onChange={e => setF(x => ({ ...x, enabled: e.target.checked }))} />
          <span>Enviar eventos</span>
        </label>
        <div className="int-op-row">
          <label className="int-op-field">
            <span>Código de teste (opcional)</span>
            <input value={f.test_event_code} onChange={e => setF(x => ({ ...x, test_event_code: e.target.value.trim() }))} placeholder="TEST12345" />
          </label>
        </div>
        <div className="int-muted">Com o código de teste preenchido, os eventos aparecem só em "Testar eventos" no Gerenciador. Apague o código quando terminar de testar.</div>
        <div className="int-op-actions">
          <button className="int-btn int-btn--primary" onClick={save} disabled={!!busy || !f.dataset_id}>{busy === 'save' ? 'Conferindo com o Meta…' : 'Salvar'}</button>
          {c?.has_token && <button className="int-btn" onClick={test} disabled={!!busy || !f.test_event_code}>{busy === 'test' ? 'Enviando…' : 'Enviar teste'}</button>}
          <span style={{ flex: 1 }} />
          {c && (delAsk
            ? <><button className="int-btn" onClick={() => setDelAsk(false)}>Cancelar</button><button className="int-btn int-btn--danger" onClick={del} disabled={!!busy}>Remover</button></>
            : <button className="int-btn" onClick={() => setDelAsk(true)}>Remover</button>)}
        </div>
      </div>
    </div>
  );
}

// ZapSign · Clicksign · Asaas — token da conta da imobiliária (api/integrations.js save_partner)
const INT_PARTNER_HINT = {
  zapsign:   { label:'Token de API', ph:'Cole o token da ZapSign' },
  clicksign: { label:'Token de acesso (API 3.0)', ph:'Cole o token da Clicksign' },
  asaas:     { label:'Chave de API', ph:'Cole a chave de API do Asaas' },
};
function IntPartnerBlock({ app, st, loading, onState, showToast }) {
  const p = st?.partners?.[app.id];
  const h = INT_PARTNER_HINT[app.id];
  const [token, setToken] = React.useState('');
  const [sandbox, setSandbox] = React.useState(!!p?.sandbox);
  const [busy, setBusy] = React.useState(null);
  const [delAsk, setDelAsk] = React.useState(false);
  React.useEffect(() => { setSandbox(!!p?.sandbox); }, [p?.sandbox]);
  if (loading && !st) return <div className="int-block"><SkelList rows={2} avatar={32} pad="8px 0" /></div>;
  if (!st) return <div className="int-block"><div className="int-muted">Não foi possível carregar. Feche e abra de novo.</div></div>;
  if (!st.can_workspace) {
    return (
      <div className="int-block"><div className="int-muted">
        {p ? <>A conta da imobiliária já está conectada{p.account_name ? <> ({p.account_name})</> : null}. Use na ficha do cliente, aba Contratos e cobranças.</>
           : 'Peça para a diretoria (ou para quem criou a imobiliária) conectar a conta.'}
      </div></div>
    );
  }
  const save = async () => {
    setBusy('save');
    try { onState(await intOpenCall({ action:'save_partner', provider: app.id, token, sandbox })); setToken(''); showToast(`${app.name} conectado.`); }
    catch (e) { showToast(e.message); }
    setBusy(null);
  };
  const del = async () => {
    setBusy('del');
    try { onState(await intOpenCall({ action:'delete_partner', provider: app.id })); showToast(`${app.name} desconectado.`); }
    catch (e) { showToast(e.message); }
    setBusy(null); setDelAsk(false);
  };
  const where = app.id === 'clicksign' ? 'da Clicksign (Configurações › Webhooks)'
    : app.id === 'asaas' ? 'do Asaas (Integrações › Webhooks, com o token de autenticação igual ao final do endereço, depois de k=)'
    : 'da ZapSign (Configurações › Integração › Webhooks)';
  return (
    <div className="int-block">
      <div className="int-block-title">Sua conta</div>
      {p ? (
        <div className="int-conn-row">
          <IntLogo app={app} size={32} />
          <div style={{ flex:1, minWidth:0 }}>
            <div className="int-conn-name">{p.account_name || app.name} {p.sandbox && <span className="int-muted">· conta de testes</span>}</div>
            <div className="int-muted">Token {p.token_hint} · conectado {_intAgo(p.updated_at)}</div>
          </div>
          {delAsk
            ? <><button className="int-btn" onClick={() => setDelAsk(false)}>Cancelar</button><button className="int-btn int-btn--danger" onClick={del} disabled={!!busy}>Desconectar</button></>
            : <button className="int-btn" onClick={() => setDelAsk(true)}>Desconectar</button>}
        </div>
      ) : <div className="int-muted">Nenhuma conta conectada ainda.</div>}

      {p?.webhook_url && (
        <div style={{ marginTop: 12 }}>
          <div className="int-muted">
            Aviso automático de {app.id === 'asaas' ? 'pagamento' : 'assinatura'}:{' '}
            {p.webhook_ok ? <span className="int-op-good">cadastrado na sua conta</span>
              : <span className="int-op-bad">cadastre o endereço abaixo no painel {where}</span>}
            {p.webhook_error && <div className="int-op-err">{p.webhook_error}</div>}
          </div>
          <IntCopy text={p.webhook_url} />
          <div className="int-muted" style={{ marginTop: 6 }}>Sem o aviso, o botão Atualizar da ficha consulta o status na hora.</div>
        </div>
      )}

      <div className="int-op-form int-op-form--col">
        <label className="int-op-field">
          <span>{h.label} {p && <em className="int-muted">(salvo — cole outro só para trocar)</em>}</span>
          <input type="password" value={token} onChange={e => setToken(e.target.value)} placeholder={p ? '••••••••' : h.ph} autoComplete="off" />
        </label>
        <label className="int-op-check">
          <input type="checkbox" checked={sandbox} onChange={e => setSandbox(e.target.checked)} />
          <span>Conta de testes (sandbox) <em className="int-muted">— cobranças e assinaturas sem valor real</em></span>
        </label>
        <div><button className="int-btn int-btn--primary" onClick={save} disabled={!!busy || (!token && !p)}>{busy === 'save' ? 'Conferindo…' : p ? 'Salvar alterações' : 'Conectar'}</button></div>
      </div>
    </div>
  );
}

// ZAP Imóveis · VivaReal · Imovelweb · Chaves na Mão — endereço @leads.horos.sale que o corretor cadastra
// no painel do portal como destino do aviso de lead (api/portal-lead-webhook.js lê o e-mail e cria o
// lead). Um endereço só por corretor, compartilhado pelos 4 cartões — cada um mostra sua própria contagem.
const INT_PORTAL_STATUS_LABEL = { created:'Lead criado', reactivated:'Reativado', duplicate:'Já era lead', ignored:'Ignorado', error:'Não entendi o e-mail' };
function IntPortalEmailBlock({ app, st, loading, onState, showToast }) {
  const [busy, setBusy] = React.useState(false);
  const [confirmRegen, setConfirmRegen] = React.useState(false);
  if (loading && !st) return <div className="int-block"><SkelList rows={2} pad="8px 0" /></div>;
  const pe = st?.portal_email;
  if (!pe) return <div className="int-block"><div className="int-muted">Não foi possível carregar o endereço. Feche e abra de novo.</div></div>;
  const info = pe.by_portal?.[app.id] || { count: 0, last_at: null };
  const recent = (pe.recent || []).filter(l => l.portal === app.id).slice(0, 6);
  const regen = async () => {
    setBusy(true);
    try { onState(await intOpenCall({ action:'regenerate_portal_email' })); showToast('Novo endereço gerado.'); }
    catch (e) { showToast(e.message); }
    setBusy(false); setConfirmRegen(false);
  };
  return (
    <div className="int-block">
      <div className="int-block-title">Seu endereço de recebimento</div>
      <div className="int-muted" style={{ marginBottom: 8 }}>Cadastre este endereço no painel do {app.name} como e-mail de notificação de lead.</div>
      <IntCopy text={pe.address} />
      <div className="int-muted" style={{ marginTop: 10 }}>
        <b>Se o painel do {app.name} não tiver um campo de "e-mail adicional"</b>, crie uma regra no seu próprio e-mail (Gmail/Outlook) para encaminhar automaticamente os avisos do {app.name} para o endereço acima — funciona igual, mesmo sem o portal ter esse campo.
      </div>
      <div className="int-muted" style={{ marginTop: 10 }}>
        {info.count > 0
          ? <span className="int-op-good">{info.count} lead{info.count > 1 ? 's' : ''} recebido{info.count > 1 ? 's' : ''} do {app.name}{info.last_at ? ` · último em ${new Date(info.last_at).toLocaleDateString('pt-BR')}` : ''}</span>
          : 'Ainda não chegou nenhum lead deste portal por aqui.'}
      </div>

      {recent.length > 0 && (
        <div style={{ marginTop: 14 }}>
          <div className="int-muted" style={{ marginBottom: 6 }}>Últimos e-mails recebidos</div>
          {recent.map((l, i) => (
            <div key={i} className="int-conn-row">
              <div style={{ flex:1, minWidth:0 }}>
                <div className="int-conn-name">{l.subject || '(sem assunto)'}</div>
                <div className="int-muted">{l.from_addr} · {_intAgo(l.created_at)}</div>
              </div>
              <span className={l.status === 'created' || l.status === 'reactivated' ? 'int-op-good' : l.status === 'duplicate' ? 'int-muted' : 'int-op-bad'}>
                {INT_PORTAL_STATUS_LABEL[l.status] || l.status}
              </span>
            </div>
          ))}
        </div>
      )}

      <div className="int-muted" style={{ marginTop: 14 }}>
        {confirmRegen ? (
          <>Gerar outro endereço desliga este (o painel do portal que ainda aponta para ele para de mandar leads).{' '}
            <button className="int-btn" disabled={busy} onClick={regen}>{busy ? 'Gerando…' : 'Confirmar'}</button>{' '}
            <button className="int-btn" onClick={() => setConfirmRegen(false)}>Cancelar</button></>
        ) : (
          <button className="int-btn" onClick={() => setConfirmRegen(true)}>Gerar outro endereço</button>
        )}
      </div>
    </div>
  );
}

// Google Ads — "Conectar com o Google" (api/google-ads.js): autoriza, escolhe a conta e liga os formulários
async function gadsCall(body) {
  const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
  const r = await fetch('/api/google-ads', {
    method: body ? 'POST' : 'GET', cache: 'no-store',
    headers: { Authorization: `Bearer ${tok}`, ...(body ? { 'Content-Type': 'application/json' } : {}) },
    body: body ? JSON.stringify(body) : undefined,
  });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) { const e = new Error(j.error || 'Não foi possível falar com o Google.'); e.data = j; e.status = r.status; throw e; }
  return j;
}

function GadsConnectBlock({ onLinked, showToast }) {
  const [st, setSt] = React.useState(null);       // { connected, email, linked, can_manage, client_id }
  const [accounts, setAccounts] = React.useState(null);
  const [acc, setAcc] = React.useState(null);
  const [forms, setForms] = React.useState(null);
  const [pick, setPick] = React.useState({});
  const [busy, setBusy] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [foreign, setForeign] = React.useState(null);   // nº de formulários com webhook de outro sistema

  const loadAccounts = async () => {
    setBusy('accounts'); setErr('');
    try { const j = await gadsCall({ action: 'accounts' }); setAccounts(j.accounts || []); if ((j.accounts || []).length === 1) chooseAcc(j.accounts[0]); }
    catch (e) { setErr(e.message); setAccounts([]); }
    setBusy(null);
  };
  React.useEffect(() => {
    gadsCall().then(j => { setSt(j); if (j.connected && j.can_manage) loadAccounts(); }).catch(e => { setSt({ connected: false, error: e.message }); });
  }, []);

  const connect = () => {
    setErr('');
    const params = new URLSearchParams({
      client_id: st.client_id, redirect_uri: 'https://horos.sale/google-oauth-callback.html', response_type: 'code',
      scope: 'https://www.googleapis.com/auth/adwords email openid', access_type: 'offline', prompt: 'consent', include_granted_scopes: 'true',
    });
    const popup = window.open(`https://accounts.google.com/o/oauth2/v2/auth?${params}`, 'google-ads-oauth', 'width=520,height=680,top=80,left=200');
    if (!popup) { setErr('O navegador bloqueou a janela do Google. Permita pop-ups para horos.sale.'); return; }
    setBusy('connect');
    const done = () => { window.removeEventListener('message', onMsg); clearInterval(timer); };
    const onMsg = async (ev) => {
      if (ev.origin !== 'https://horos.sale' || !ev.data) return;
      if (ev.data.type === 'google-oauth-error') { done(); setBusy(null); setErr('O Google não autorizou: ' + ev.data.error); return; }
      if (!ev.data.code) return;
      done();
      try {
        const j = await gadsCall({ action: 'connect', code: ev.data.code });
        setSt(s => ({ ...s, connected: true, email: j.email, linked: s?.linked || [] }));
        setAccounts(j.accounts || []);
        if (j.error) setErr(j.error);
        if ((j.accounts || []).length === 1) chooseAcc(j.accounts[0]);
        showToast('Google Ads conectado.');
      } catch (e) { setErr(e.message); }
      setBusy(null);
    };
    window.addEventListener('message', onMsg);
    const timer = setInterval(() => { if (popup.closed) { setTimeout(() => { done(); setBusy(b => b === 'connect' ? null : b); }, 800); } }, 700);
  };

  const chooseAcc = async (a) => {
    setAcc(a); setForms(null); setForeign(null); setErr('');
    if (!a) return;
    setBusy('forms');
    try {
      const j = await gadsCall({ action: 'forms', customer_id: a.id, login_customer_id: a.login });
      setForms(j.forms || []);
      setPick(Object.fromEntries((j.forms || []).map(f => [f.id, !f.webhook || f.horos])));
    } catch (e) { setErr(e.message); setForms([]); }
    setBusy(null);
  };

  const link = async (force) => {
    const ids = Object.keys(pick).filter(k => pick[k]);
    if (!ids.length) return;
    setBusy('link'); setErr('');
    try {
      const j = await gadsCall({ action: 'link', customer_id: acc.id, login_customer_id: acc.login, account_name: acc.name, asset_ids: ids, force: !!force });
      setForeign(null);
      setSt(s => ({ ...s, linked: j.linked }));
      showToast(j.failed ? `${j.linked_forms} formulário(s) ligado(s); ${j.failed} falharam.` : `Pronto: ${j.linked_forms} formulário(s) mandando leads para o Horos.`);
      if (j.partial_error) setErr(j.partial_error);
      chooseAcc(acc);
      onLinked && onLinked();
    } catch (e) {
      if (e.status === 409 && e.data?.error === 'foreign_webhook') setForeign(e.data.count);
      else setErr(e.message);
    }
    setBusy(null);
  };

  if (!st) return <div className="int-block"><SkelList rows={1} avatar={32} pad="8px 0" /></div>;
  const nPick = Object.values(pick).filter(Boolean).length;

  return (
    <div className="int-block">
      <div className="int-block-title">Conectar com o Google</div>
      {!st.connected ? (
        <>
          <div className="int-muted" style={{ marginBottom: 10 }}>Entre com a conta Google que administra os anúncios. O Horos lista seus formulários de lead e liga todos com um clique — sem copiar e colar.</div>
          {st.can_manage
            ? <button className="int-gbtn" onClick={connect} disabled={busy === 'connect'}>
                <svg width="18" height="18" viewBox="0 0 48 48"><path fill="#FFC107" d="M43.6 20.5H42V20H24v8h11.3C33.7 32.7 29.2 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.1 6.1 29.3 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.4-.4-3.5z"/><path fill="#FF3D00" d="m6.3 14.7 6.6 4.8C14.7 15.1 19 12 24 12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.1 6.1 29.3 4 24 4 16.3 4 9.7 8.3 6.3 14.7z"/><path fill="#4CAF50" d="M24 44c5.2 0 9.9-2 13.4-5.2l-6.2-5.2C29.2 35.1 26.7 36 24 36c-5.2 0-9.6-3.3-11.3-8l-6.5 5C9.5 39.6 16.2 44 24 44z"/><path fill="#1976D2" d="M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.2-4.1 5.6l6.2 5.2C37 39.2 44 34 44 24c0-1.3-.1-2.4-.4-3.5z"/></svg>
                {busy === 'connect' ? 'Continue na janela do Google…' : 'Conectar com o Google'}
              </button>
            : <div className="int-muted">Peça para a diretoria (ou para quem criou a imobiliária) conectar o Google Ads.</div>}
        </>
      ) : (
        <>
          <div className="int-conn-row">
            <IntLogo app={{ id:'gads', logo:'gads' }} size={32} />
            <div style={{ flex:1, minWidth:0 }}>
              <div className="int-conn-name">{st.email || 'Conta Google conectada'}</div>
              <div className="int-muted">{(st.linked || []).length ? (st.linked || []).map(l => `${l.name}: ${l.forms} formulário${l.forms === 1 ? '' : 's'}`).join(' · ') : 'Nenhum formulário ligado ainda'}</div>
            </div>
            {st.can_manage && <button className="int-btn" onClick={async () => { try { await gadsCall({ action: 'disconnect' }); setSt(s => ({ ...s, connected: false, linked: [] })); setAccounts(null); setAcc(null); setForms(null); } catch (e) { setErr(e.message); } }}>Desconectar</button>}
          </div>

          {st.can_manage && (
            <div className="int-op-form int-op-form--col">
              <label className="int-op-field">
                <span>Conta do Google Ads</span>
                <select value={acc?.id || ''} onChange={e => chooseAcc((accounts || []).find(x => x.id === e.target.value) || null)} disabled={busy === 'accounts'}>
                  <option value="">{busy === 'accounts' ? 'Carregando contas…' : (accounts || []).length ? 'Escolha a conta…' : 'Nenhuma conta encontrada'}</option>
                  {(accounts || []).map(x => <option key={x.id} value={x.id}>{x.name} · {x.id.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3')}{x.test ? ' (teste)' : ''}</option>)}
                </select>
              </label>
              {acc && (
                busy === 'forms' ? <SkelList rows={2} avatar={0} pad="6px 0" />
                : forms && forms.length === 0 ? <div className="int-muted">Essa conta não tem formulários de lead. Crie um na campanha (Recursos › Formulário de lead) e clique em <button className="int-link" onClick={() => chooseAcc(acc)}>atualizar</button>.</div>
                : forms && (
                  <>
                    <div className="int-op-field">
                      <span>Formulários de lead</span>
                      <div className="int-op-stages">
                        {forms.map(f => (
                          <label key={f.id} className="int-op-stage int-op-stage--2">
                            <input type="checkbox" checked={!!pick[f.id]} onChange={e => setPick(p => ({ ...p, [f.id]: e.target.checked }))} />
                            <span>{f.name}{f.headline && f.headline !== f.name ? <em className="int-muted"> · {f.headline}</em> : null}</span>
                            <span className={f.horos ? 'int-op-good' : f.webhook ? 'int-op-bad' : 'int-muted'} style={{ fontSize: 12 }}>
                              {f.horos ? 'Ligado no Horos' : f.webhook ? 'Manda para outro sistema' : 'Sem integração'}
                            </span>
                          </label>
                        ))}
                      </div>
                    </div>
                    {foreign ? (
                      <div className="int-op-fresh">
                        <b>{foreign} formulário{foreign > 1 ? 's' : ''} já manda{foreign > 1 ? 'm' : ''} leads para outro sistema.</b> O Google aceita só um webhook por formulário: se trocar, os leads desse{foreign > 1 ? 's' : ''} formulário{foreign > 1 ? 's' : ''} passam a vir só para o Horos.
                        <div className="int-op-actions" style={{ marginTop: 8 }}>
                          <button className="int-btn" onClick={() => setForeign(null)}>Cancelar</button>
                          <button className="int-btn int-btn--primary" onClick={() => link(true)} disabled={busy === 'link'}>Trocar para o Horos</button>
                        </div>
                      </div>
                    ) : (
                      <div><button className="int-btn int-btn--primary" onClick={() => link(false)} disabled={busy === 'link' || !nPick}>{busy === 'link' ? 'Ligando…' : `Ligar ${nPick} formulário${nPick === 1 ? '' : 's'} no Horos`}</button></div>
                    )}
                  </>
                )
              )}
            </div>
          )}
        </>
      )}
      {err && <div className="int-op-err" style={{ marginTop: 10 }}>{err}</div>}
    </div>
  );
}

// ── Estoque de imóveis (2026-10-14): Vista, Jetimob, Kenlo, Imobzi (link do XML de portais) e CV CRM (API).
// Os imóveis vão para a tabela properties — a IA usa para responder e recomendar. Sincroniza 1×/dia sozinho.
const INT_FEED_SYSTEMS = { vista:'Vista', jetimob:'Jetimob', kenlo:'Kenlo', imobzi:'Imobzi' };
const _intBRL = v => v ? Number(v).toLocaleString('pt-BR', { style:'currency', currency:'BRL', maximumFractionDigits: 0 }) : '—';
function IntPropertiesBlock({ app, st, loading, onState, showToast }) {
  const isCv = app.id === 'cvcrm';
  const p = st?.properties?.[app.id];
  const [url, setUrl] = React.useState('');
  const [cv, setCv] = React.useState({ domain:'', email:'', token:'' });
  const [busy, setBusy] = React.useState(null);
  const [delAsk, setDelAsk] = React.useState(false);
  const [lastErr, setLastErr] = React.useState('');
  React.useEffect(() => { if (p?.feed_url) setUrl(p.feed_url); if (p?.domain) setCv(c => ({ ...c, domain: p.domain })); }, [p?.feed_url, p?.domain]);
  if (loading && !st) return <div className="int-block"><SkelList rows={2} avatar={32} pad="8px 0" /></div>;
  if (!st) return <div className="int-block"><div className="int-muted">Não foi possível carregar. Feche e abra de novo.</div></div>;

  const run = async (kind, body, okMsg) => {
    setBusy(kind); setLastErr('');
    try {
      const j = await intOpenCall(body);
      onState(j);
      if (j.sync && !j.sync.ok) { setLastErr(j.sync.error); showToast('Conectado, mas a importação falhou. Veja o motivo abaixo.'); }
      else if (okMsg) showToast(typeof okMsg === 'function' ? okMsg(j) : okMsg);
    } catch (e) { setLastErr(e.message); showToast(e.message); }
    setBusy(null); setDelAsk(false);
  };
  const save = () => isCv
    ? run('save', { action:'save_cvcrm', ...cv }, j => `CV CRM conectado: ${j.sync?.count ?? 0} empreendimento(s).`)
    : run('save', { action:'save_feed', provider: app.id, feed_url: url }, j => `${j.sync?.count ?? 0} imóveis importados do ${app.name}.`);
  const sync = () => run('sync', { action:'sync_properties', provider: app.id }, j => `Atualizado: ${j.sync?.count ?? 0} ${isCv ? 'empreendimento(s)' : 'imóveis'}.`);
  const del = () => run('del', { action:'delete_properties', provider: app.id }, `${app.name} desconectado. Os imóveis importados foram retirados.`);
  const word = isCv ? ['empreendimento disponível', 'empreendimentos disponíveis'] : ['imóvel disponível', 'imóveis disponíveis'];

  return (
    <>
      <div className="int-block">
        <div className="int-block-title">Estoque no Horos</div>
        {p ? (
          <>
            <div className="int-conn-row">
              <IntLogo app={app} size={32} />
              <div style={{ flex:1, minWidth:0 }}>
                <div className="int-conn-name">{p.available} {p.available === 1 ? word[0] : word[1]}</div>
                <div className="int-muted">
                  {p.last_sync_ok === false
                    ? <span className="int-op-bad">Última tentativa falhou {_intAgo(p.last_sync_at)}</span>
                    : <>Atualizado {_intAgo(p.last_sync_at)}{p.format ? ` · formato ${p.format}` : ''} · atualiza sozinho todo dia</>}
                </div>
                {p.last_sync_error && <div className="int-op-err">{p.last_sync_error}</div>}
              </div>
              <button className="int-btn" onClick={sync} disabled={!!busy || !st.can_workspace}>{busy === 'sync' ? 'Atualizando…' : 'Atualizar agora'}</button>
            </div>
            {(p.sample || []).length > 0 && (
              <div className="int-prop-list">
                {p.sample.map(x => (
                  <a key={x.id} className="int-prop" href={/^https?:\/\//i.test(x.url || '') ? x.url : undefined} target="_blank" rel="noopener noreferrer" onClick={e => { if (!/^https?:\/\//i.test(x.url || '')) e.preventDefault(); }}>
                    {x.media_urls?.[0] ? <img src={x.media_urls[0]} alt="" loading="lazy" /> : <span className="int-prop-ph"><Icon name="home" size={16} /></span>}
                    <span className="int-prop-txt">
                      <b>{x.name}</b>
                      <span className="int-muted">
                        {[x.address?.bairro, x.address?.cidade].filter(Boolean).join(', ') || '—'}
                        {isCv ? ` · ${x.details?.unidades_disponiveis ?? 0} unidade(s)` : ` · ${_intBRL(x.price)}${x.details?.quartos ? ` · ${x.details.quartos} quarto(s)` : ''}`}
                      </span>
                    </span>
                  </a>
                ))}
              </div>
            )}
            <div className="int-muted" style={{ marginTop: 10 }}>A IA do Horos usa esse estoque para responder e sugerir {isCv ? 'empreendimentos' : 'imóveis'} parecidos com o que cada cliente procura.</div>
          </>
        ) : <div className="int-muted">Nenhum estoque importado ainda.</div>}
      </div>

      {!st.can_workspace ? (
        <div className="int-block"><div className="int-muted">Peça para a diretoria (ou para quem criou a imobiliária) conectar o {app.name}.</div></div>
      ) : (
        <div className="int-block">
          <div className="int-block-title">{p ? 'Conexão' : `Conectar o ${app.name}`}</div>
          {isCv ? (
            <div className="int-muted">No CV CRM: <b>Configurações › Integrações › APIs › Configurar</b> — escolha um usuário administrador, gere um token sem validade e copie o e-mail desse usuário e o token.</div>
          ) : (
            <div className="int-muted">
              O {app.name} já gera um arquivo com todos os seus imóveis para mandar aos portais. Na área de integração com portais do {app.name},
              copie o <b>link do arquivo XML do VivaReal (ou do ZAP)</b> e cole abaixo. Se não achar, peça ao suporte do {app.name} "o link do XML de imóveis no formato VivaReal".
            </div>
          )}
          <div className="int-op-form int-op-form--col">
            {isCv ? (
              <>
                <label className="int-op-field"><span>Domínio do CV CRM</span>
                  <input value={cv.domain} onChange={e => setCv({ ...cv, domain: e.target.value })} placeholder="suaempresa.cvcrm.com.br" autoComplete="off" /></label>
                <label className="int-op-field"><span>E-mail do usuário {p?.email_hint && <em className="int-muted">(salvo: {p.email_hint})</em>}</span>
                  <input value={cv.email} onChange={e => setCv({ ...cv, email: e.target.value })} placeholder={p ? 'mantém o salvo se ficar vazio' : 'admin@suaempresa.com.br'} autoComplete="off" /></label>
                <label className="int-op-field"><span>Token {p?.token_hint && <em className="int-muted">(salvo {p.token_hint} — cole outro só para trocar)</em>}</span>
                  <input type="password" value={cv.token} onChange={e => setCv({ ...cv, token: e.target.value })} placeholder={p ? '••••••••' : 'Cole o token do CV CRM'} autoComplete="off" /></label>
              </>
            ) : (
              <label className="int-op-field"><span>Link do arquivo XML</span>
                <input value={url} onChange={e => setUrl(e.target.value)} placeholder="https://…/imoveis.xml" autoComplete="off" /></label>
            )}
            {lastErr && <div className="int-op-err">{lastErr}</div>}
            <div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
              <button className="int-btn int-btn--primary" onClick={save}
                disabled={!!busy || (isCv ? (!cv.domain || (!p && (!cv.email || !cv.token))) : !url.trim())}>
                {busy === 'save' ? 'Importando…' : p ? 'Salvar e importar de novo' : 'Conectar e importar'}
              </button>
              {p && (delAsk
                ? <><button className="int-btn" onClick={() => setDelAsk(false)}>Cancelar</button><button className="int-btn int-btn--danger" onClick={del} disabled={!!busy}>Desconectar e tirar os imóveis</button></>
                : <button className="int-btn" onClick={() => setDelAsk(true)}>Desconectar</button>)}
            </div>
          </div>
        </div>
      )}
    </>
  );
}

// ── Api4Com (2026-10-14): a diretoria cola o token; cada pessoa informa o próprio ramal.
// Ligar = botão no Horos 360 (data.jsx LeadCallButton); a ligação e a gravação ficam no lead (lead_calls).
function IntApi4ComBlock({ app, st, loading, onState, showToast }) {
  const a4 = st?.api4com;
  const [token, setToken] = React.useState('');
  const [ext, setExt] = React.useState('');
  const [busy, setBusy] = React.useState(null);
  const [delAsk, setDelAsk] = React.useState(false);
  React.useEffect(() => { setExt(a4?.my_extension || ''); }, [a4?.my_extension]);
  if (loading && !st) return <div className="int-block"><SkelList rows={2} avatar={32} pad="8px 0" /></div>;
  if (!st) return <div className="int-block"><div className="int-muted">Não foi possível carregar. Feche e abra de novo.</div></div>;
  const run = async (kind, body, msg) => {
    setBusy(kind);
    try { onState(await intOpenCall(body)); if (msg) showToast(msg); if (kind === 'save') setToken(''); window.horosCallSetupReset && window.horosCallSetupReset(); }
    catch (e) { showToast(e.message); }
    setBusy(null); setDelAsk(false);
  };
  return (
    <>
      <div className="int-block">
        <div className="int-block-title">Conta da Api4Com</div>
        {a4 ? (
          <div className="int-conn-row">
            <IntLogo app={app} size={32} />
            <div style={{ flex:1, minWidth:0 }}>
              <div className="int-conn-name">{a4.account || 'Conta conectada'}</div>
              <div className="int-muted">Token {a4.token_hint} · {a4.extensions_count} ramal(is) informado(s) · conectado {_intAgo(a4.updated_at)}</div>
              <div className="int-muted">Registro das ligações: {a4.webhook_ok ? <span className="int-op-good">ativo</span> : <span className="int-op-bad">não foi possível ativar</span>}</div>
              {a4.webhook_error && <div className="int-op-err">{a4.webhook_error}</div>}
            </div>
            {st.can_workspace && (delAsk
              ? <><button className="int-btn" onClick={() => setDelAsk(false)}>Cancelar</button><button className="int-btn int-btn--danger" onClick={() => run('del', { action:'delete_api4com' }, 'Api4Com desconectada.')} disabled={!!busy}>Desconectar</button></>
              : <button className="int-btn" onClick={() => setDelAsk(true)}>Desconectar</button>)}
          </div>
        ) : <div className="int-muted">{st.can_workspace ? 'Nenhuma conta conectada ainda.' : 'Peça para a diretoria (ou para quem criou a imobiliária) conectar a conta da Api4Com.'}</div>}
        {st.can_workspace && (
          <div className="int-op-form int-op-form--col">
            <div className="int-muted">Na Api4Com, gere um <b>token de acesso sem validade</b> (Configurações › Tokens, ou peça ao suporte da Api4Com) e cole abaixo.</div>
            <label className="int-op-field"><span>Token de acesso {a4 && <em className="int-muted">(salvo — cole outro só para trocar)</em>}</span>
              <input type="password" value={token} onChange={e => setToken(e.target.value)} placeholder={a4 ? '••••••••' : 'Cole o token da Api4Com'} autoComplete="off" /></label>
            <div><button className="int-btn int-btn--primary" onClick={() => run('save', { action:'save_api4com', token }, 'Api4Com conectada.')} disabled={!!busy || (!token && !a4)}>{busy === 'save' ? 'Conferindo…' : a4 ? 'Salvar alterações' : 'Conectar'}</button></div>
          </div>
        )}
      </div>
      {a4 && (
        <div className="int-block">
          <div className="int-block-title">Seu ramal</div>
          <div className="int-muted">O número do seu ramal na Api4Com (ex.: 1000). Ao clicar em Ligar no Horos 360, o seu ramal toca primeiro e, quando você atende, a Api4Com liga para o cliente.</div>
          <div className="int-op-form">
            <label className="int-op-field" style={{ maxWidth: 200 }}><span>Ramal</span>
              <input inputMode="numeric" value={ext} onChange={e => setExt(e.target.value.replace(/\D/g, ''))} placeholder="1000" /></label>
            <button className="int-btn int-btn--primary" onClick={() => run('ext', { action:'set_extension', extension: ext }, ext ? 'Ramal salvo.' : 'Ramal removido.')} disabled={!!busy || ext === (a4.my_extension || '')}>{busy === 'ext' ? 'Salvando…' : 'Salvar ramal'}</button>
          </div>
        </div>
      )}
    </>
  );
}

// ── Conversões do Google Ads (2026-10-14): usa a conexão do card Google Ads (formulários). Cada etapa do
// funil pode virar uma conversão "importar cliques"; Fechado manda o valor da venda. Só leads com gclid.
function IntGadsConvBlock({ st, showToast }) {
  const [g, setG] = React.useState(null);
  const [accounts, setAccounts] = React.useState(null);
  const [acc, setAcc] = React.useState(null);
  const [actions, setActions] = React.useState(null);
  const [map, setMap] = React.useState({});
  const [busy, setBusy] = React.useState(null);
  const [err, setErr] = React.useState('');
  const stages = (st?.stages || []).filter(s => !/^arquiv/i.test(s.id) && !/arquiv/i.test(s.name));

  const chooseAcc = async (a, keepMap) => {
    setAcc(a); setActions(null); setErr('');
    if (!a) return;
    setBusy('actions');
    try { const j = await gadsCall({ action:'conv_actions', customer_id: a.id, login_customer_id: a.login }); setActions(j.actions || []); if (!keepMap) setMap({}); }
    catch (e) { setErr(e.message); setActions([]); }
    setBusy(null);
  };
  React.useEffect(() => {
    gadsCall().then(async j => {
      setG(j);
      if (!j.connected || !j.can_manage) return;
      if (j.conv?.stages) setMap(j.conv.stages);
      try {
        const r = await gadsCall({ action:'accounts' });
        setAccounts(r.accounts || []);
        const pre = (r.accounts || []).find(x => x.id === j.conv?.customer_id) || ((r.accounts || []).length === 1 ? r.accounts[0] : null);
        if (pre) chooseAcc(pre, true);
      } catch (e) { setErr(e.message); setAccounts([]); }
    }).catch(e => setG({ connected:false, error: e.message }));
  }, []);

  const create = async (col, name) => {
    setBusy('create:' + col); setErr('');
    try {
      const j = await gadsCall({ action:'conv_create', customer_id: acc.id, login_customer_id: acc.login, name: `Horos — ${name}`, category: col === 'fechado' ? 'PURCHASE' : 'QUALIFIED_LEAD' });
      setActions(a => [...(a || []), { resource: j.resource, name: j.name }]);
      setMap(m => ({ ...m, [col]: j.resource }));
    } catch (e) { setErr(e.message); }
    setBusy(null);
  };
  const save = async (enabled = true) => {
    setBusy('save'); setErr('');
    try {
      const names = {}; Object.entries(map).forEach(([c, r]) => { names[c] = (actions || []).find(x => x.resource === r)?.name || ''; });
      const j = await gadsCall({ action:'conv_save', customer_id: acc.id, login_customer_id: acc.login, account_name: acc.name, stages: map, names, enabled });
      setG(x => ({ ...x, conv: j.conv })); showToast('Conversões do Google Ads ligadas.');
    } catch (e) { setErr(e.message); }
    setBusy(null);
  };
  const off = async () => {
    setBusy('off');
    try { const j = await gadsCall({ action:'conv_off' }); setG(x => ({ ...x, conv: j.conv })); showToast('Conversões desligadas.'); }
    catch (e) { setErr(e.message); }
    setBusy(null);
  };

  if (!g) return <div className="int-block"><SkelList rows={2} avatar={32} pad="8px 0" /></div>;
  if (!g.connected) return (
    <div className="int-block">
      <div className="int-muted">Primeiro conecte o Google Ads no cartão <b>Google Ads (formulários)</b> — as conversões usam a mesma conta.</div>
      <div style={{ marginTop: 10 }}><a className="int-btn int-btn--primary" href="integrations.html?app=gads">Abrir Google Ads (formulários)</a></div>
      {g.error && <div className="int-op-err">{g.error}</div>}
    </div>
  );
  if (!g.can_manage) return <div className="int-block"><div className="int-muted">{g.conv?.enabled ? 'As conversões estão ligadas pela diretoria.' : 'Peça para a diretoria ligar as conversões do Google Ads.'}</div></div>;
  const conv = g.conv;
  return (
    <div className="int-block">
      <div className="int-block-title">Conversões de volta para o Google Ads</div>
      {conv?.enabled && (
        <div className="int-conn-row">
          <IntLogo app={{ id:'gads', logo:'gads' }} size={32} />
          <div style={{ flex:1, minWidth:0 }}>
            <div className="int-conn-name">Ligado · {conv.account_name}</div>
            <div className="int-muted">{conv.sent || 0} conversão(ões) enviada(s) · última {_intAgo(conv.last_at)}</div>
            {conv.last_error && <div className="int-op-err">{conv.last_error}</div>}
          </div>
          <button className="int-btn" onClick={off} disabled={!!busy}>Desligar</button>
        </div>
      )}
      <div className="int-muted" style={{ marginTop: 6 }}>
        Quando um lead que veio de anúncio do Google (tem o código do clique, gclid) chega a uma etapa escolhida abaixo, o Horos avisa o Google Ads.
        Em <b>Fechado</b> vai junto o valor da venda. Assim o Google aprende quais cliques viram cliente e otimiza as campanhas.
      </div>
      <div className="int-op-form int-op-form--col">
        <label className="int-op-field"><span>Conta do Google Ads</span>
          <select value={acc?.id || ''} onChange={e => chooseAcc((accounts || []).find(x => x.id === e.target.value) || null)}>
            <option value="">{accounts == null ? 'Carregando contas…' : 'Escolha a conta'}</option>
            {(accounts || []).map(x => <option key={x.id} value={x.id}>{x.name}{x.test ? ' (teste)' : ''}</option>)}
          </select></label>
        {acc && actions == null && <div className="int-muted">Carregando conversões…</div>}
        {acc && actions && (
          <div className="int-gconv-list">
            {stages.map(s => (
              <div key={s.id} className="int-gconv-row">
                <span className="int-gconv-stage">{s.name}{s.id === 'fechado' && <em className="int-muted"> · com valor</em>}</span>
                <select value={map[s.id] || ''} onChange={e => setMap(m => { const n = { ...m }; if (e.target.value) n[s.id] = e.target.value; else delete n[s.id]; return n; })}>
                  <option value="">Não enviar</option>
                  {actions.map(x => <option key={x.resource} value={x.resource}>{x.name}</option>)}
                </select>
                {!map[s.id] && <button className="int-btn" onClick={() => create(s.id, s.name)} disabled={!!busy}>{busy === 'create:' + s.id ? 'Criando…' : 'Criar'}</button>}
              </div>
            ))}
          </div>
        )}
        {err && <div className="int-op-err">{err}</div>}
        {acc && actions && <div><button className="int-btn int-btn--primary" onClick={() => save(true)} disabled={!!busy || !Object.keys(map).length}>{busy === 'save' ? 'Salvando…' : conv?.enabled ? 'Salvar alterações' : 'Ligar conversões'}</button></div>}
        <div className="int-muted">Enquanto o Google não libera o acesso do Horos às contas reais (pedido em análise), só contas de teste aparecem.</div>
      </div>
    </div>
  );
}

// ── Gmail (2026-10-14): conta Gmail de cada pessoa, só para enviar (escopo gmail.send) ──
function IntGmailBlock({ app, showToast, onChange }) {
  const [g, setG] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  React.useEffect(() => { window.horosGmailCall().then(setG).catch(e => setG({ connected: false, error: e.message })); }, []);
  const connect = async () => {
    setBusy(true); setErr('');
    try { const j = await window.horosGmailConnect(); setG(x => ({ ...x, connected: true, email: j.email })); showToast('Gmail conectado.'); onChange && onChange(true, j.email); }
    catch (e) { if (e.message !== 'closed') setErr(e.message); }
    setBusy(false);
  };
  const disconnect = async () => {
    setBusy(true);
    try { await window.horosGmailCall({ action: 'disconnect' }); setG(x => ({ ...x, connected: false, email: null })); showToast('Gmail desconectado.'); onChange && onChange(false); }
    catch (e) { setErr(e.message); }
    setBusy(false);
  };
  if (!g) return <div className="int-block"><SkelList rows={1} avatar={32} pad="8px 0" /></div>;
  return (
    <div className="int-block">
      <div className="int-block-title">Seu Gmail</div>
      {g.connected ? (
        <div className="int-conn-row">
          <IntLogo app={app} size={32} />
          <div style={{ flex:1, minWidth:0 }}>
            <div className="int-conn-name">{g.email || 'Conta conectada'}</div>
            <div className="int-muted">Use o botão E-mail no Horos 360 de cada cliente.</div>
          </div>
          <button className="int-btn" onClick={disconnect} disabled={busy}>Desconectar</button>
        </div>
      ) : (
        <>
          <div className="int-muted">Cada pessoa conecta o próprio Gmail. O Horos pede só a permissão de <b>enviar</b> e-mails — não lê a sua caixa de entrada.</div>
          <div style={{ marginTop: 10 }}><button className="int-btn int-btn--primary" onClick={connect} disabled={busy}>{busy ? 'Conectando…' : 'Conectar o Gmail'}</button></div>
        </>
      )}
      {(err || g.error) && <div className="int-op-err">{err || g.error}</div>}
    </div>
  );
}

function IntOpenPanel({ app, st, loading, onState, showToast }) {
  if (loading && !st) return <div className="int-block"><SkelList rows={2} avatar={32} pad="8px 0" /></div>;
  if (!st) return <div className="int-block"><div className="int-muted">Não foi possível carregar. Feche e abra de novo.</div></div>;
  if (app.id === 'meta-capi') return <IntCapiBlock st={st} onState={onState} showToast={showToast} />;   // dentro do card Facebook e Instagram Ads
  if (app.id === 'gads') {
    const gk = (st.keys || []).filter(k => k.kind === 'gads');
    return (
      <>
        <GadsConnectBlock showToast={showToast} onLinked={() => intOpenCall().then(onState).catch(() => {})} />
        <details className="int-op-manual">
          <summary>Configurar manualmente (copiar e colar)</summary>
          <div className="int-block">
            <div className="int-block-title">Endereço do webhook</div>
            <div className="int-muted" style={{ marginBottom: 6 }}>No Google Ads: campanha › Recursos › Formulário de lead › Opções de entrega de leads › Integração de webhook. Cole em "URL do webhook":</div>
            <IntCopy text={`${INT_API_BASE}/google-ads`} />
            {gk.length > 0 && <div className="int-muted" style={{ marginTop: 8 }}>No campo "Chave", use a chave gerada abaixo (ela só aparece na hora de gerar; se perdeu, revogue e gere outra).</div>}
          </div>
          <IntKeysBlock st={st} kinds={['gads']} defaultKind="gads" title="Chaves do Google Ads" createLabel="Gerar chave do Google Ads" onState={onState} showToast={showToast} />
        </details>
      </>
    );
  }
  if (app.id === 'zapier' || app.id === 'make') {
    const zap = app.id === 'zapier';
    return (
      <>
        <IntKeysBlock st={st} kinds={[app.id]} defaultKind={app.id} title={zap ? 'Chaves do Zapier' : 'Chaves do Make'} onState={onState} showToast={showToast} />
        <div className="int-muted" style={{ margin: '0 0 10px' }}>Passo a passo e rotas em <a className="int-link" href="developers.html#zapier" target="_blank" rel="noopener noreferrer">Documentação da API</a>.</div>
        <IntHooksBlock st={st} source={app.id} onState={onState} showToast={showToast}
          hint={zap ? 'Cole o endereço do Catch Hook do Zapier e clique em Testar para ele enxergar os campos.'
                    : 'Cole o endereço do Custom webhook do Make e clique em Testar para ele reconhecer a estrutura.'} />
      </>
    );
  }
  return (
    <>
      <IntKeysBlock st={st} kinds={['api', 'zapier', 'make']} defaultKind="api" title="Chaves de API" onState={onState} showToast={showToast} />
      <IntHooksBlock st={st} onState={onState} showToast={showToast} />
      <IntApiDocs />
    </>
  );
}

// ── Situação das integrações (2026-10-14) ────────────────────────────────────
// "Gerenciar aplicativos instalados" virou a situação de cada integração: funcionando / precisa de
// atenção / parada, o problema em português e o botão para corrigir. Dados: GET /api/integrations?health=1.
const INT_HEALTH_LABEL = { ok: 'Funcionando', warn: 'Precisa de atenção', error: 'Parada' };
function IntHealthPill({ status }) {
  if (!status) return <IntStatus status="connected" />;
  return <span className={`int-hp int-hp--${status}`}><span className="int-hp-dot" />{INT_HEALTH_LABEL[status]}</span>;
}

function IntHealthView({ apps, health, loading, onReload, summaryOf, onOpen }) {
  const items = health?.items || [];
  const byApp = {};
  items.forEach(it => { (byApp[it.id] = byApp[it.id] || []).push(it); });
  const rank = { error: 0, warn: 1, ok: 2 };
  const worst = (id) => (byApp[id] || []).reduce((w, it) => (w == null || rank[it.status] < rank[w] ? it.status : w), null);
  // conectados + os que a verificação achou (ex.: WhatsApp desconectado continua aparecendo)
  const ids = [...new Set([...apps.map(a => a.id), ...Object.keys(byApp)])];
  const rows = ids.map(id => INT_APPS.find(a => a.id === id)).filter(Boolean)
    .sort((a, b) => (rank[worst(a.id)] ?? 2) - (rank[worst(b.id)] ?? 2));
  const n = (st) => items.filter(i => i.status === st).length;
  const activity = health?.activity || [];

  return (
    <>
      <section className="int-section">
        <div className="int-results-head">
          <h2 className="int-h2">Situação das integrações <span className="int-count">{rows.length}</span></h2>
          <button className="int-link" onClick={onReload} disabled={loading}>
            {loading ? 'Verificando…' : <>Verificar de novo{health?.checked_at ? ` · ${new Date(health.checked_at).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}` : ''}</>}
          </button>
        </div>
        {health && rows.length > 0 && (
          <div className="int-hsum">
            <span><IntHealthPill status="ok" /> {n('ok')}</span>
            <span><IntHealthPill status="warn" /> {n('warn')}</span>
            <span><IntHealthPill status="error" /> {n('error')}</span>
          </div>
        )}
        {!health && loading ? (
          <div className="int-installed"><SkelList rows={3} avatar={36} pad="14px 18px" /></div>
        ) : rows.length === 0 ? (
          <div className="int-empty">Nenhum aplicativo conectado ainda. Comece pelo WhatsApp ou pelo Facebook e Instagram Ads.</div>
        ) : (
          <div className="int-installed">
            {rows.map(a => {
              const its = byApp[a.id] || [];
              const w = worst(a.id);
              const fix = its.find(it => it.status !== 'ok' && it.fix)?.fix;
              return (
                <div key={a.id} className={`int-inst-row int-hrow${w && w !== 'ok' ? ` is-${w}` : ''}`}>
                  <IntLogo app={a} size={36} />
                  <div className="int-hrow-main">
                    <div className="int-app-name">{a.name}</div>
                    {its.length ? its.map((it, i) => (
                      <div key={i} className="int-hrow-line">
                        <span className="int-hrow-title">{it.sub ? `${it.sub}: ` : ''}{it.title}</span>
                        <span className="int-muted">
                          {it.detail}{it.last_at ? ` · ${_intAgo(it.last_at)}` : ''}
                        </span>
                      </div>
                    )) : <div className="int-app-by">{summaryOf(a) || 'Conectado'}</div>}
                  </div>
                  <IntHealthPill status={w} />
                  {fix
                    ? <button className="int-btn int-btn--primary" onClick={() => onOpen(INT_APPS.find(x => x.id === fix.app) || a)}>{fix.label}</button>
                    : <button className="int-btn" onClick={() => onOpen(a)}>Gerenciar</button>}
                </div>
              );
            })}
          </div>
        )}
      </section>

      {activity.length > 0 && (
        <section className="int-section">
          <h2 className="int-h2">Últimos leads que chegaram pelas integrações</h2>
          <div className="int-installed">
            {activity.map(l => {
              const o = window.leadOriginInfo ? window.leadOriginInfo(l.origem_lead) : { label: l.origem_lead };
              return (
                <button key={l.id} type="button" className="int-inst-row int-act-row" onClick={() => window.horosOpenLeadSheet && window.horosOpenLeadSheet(l.id)}>
                  <span className="int-act-av">{initials(l.name)}</span>
                  <span className="int-hrow-main">
                    <span className="int-app-name">{l.name || 'Sem nome'}</span>
                    <span className="int-muted">{o.label}</span>
                  </span>
                  <span className="int-muted">{_intAgo(l.created_at)}</span>
                </button>
              );
            })}
          </div>
        </section>
      )}
    </>
  );
}

function IntegracoesPage() {
  const [metaConns, setMetaConns]             = React.useState([]);
  const [loadingMeta, setLoadingMeta]         = React.useState(true);
  const [showMeta, setShowMeta]               = React.useState(false);
  const [disconnecting, setDisconnecting]     = React.useState(null);
  const [toast, setToast]                     = React.useState('');
  const [googleConnected, setGoogleConnected] = React.useState(false);
  const [googleEmail, setGoogleEmail]         = React.useState(null);
  const [googleLoading, setGoogleLoading]     = React.useState(true);
  const [wa, setWa]                           = React.useState({ loading:true, status:null, phone:null });
  const [openSt, setOpenSt]                   = React.useState(null);   // API, webhooks, Google Ads, CAPI (/api/integrations)
  const [openLoading, setOpenLoading]         = React.useState(true);

  const [query, setQuery]       = React.useState('');
  const [search, setSearch]     = React.useState('');   // aplicado no "Buscar"
  const [goal, setGoal]         = React.useState(() => {   // ?meta=atrair (links da página Sobre as integrações)
    try { const m = new URLSearchParams(location.search).get('meta'); return INT_GOALS.some(g => g.id === m) ? m : null; } catch { return null; }
  });
  const [view, setView]         = React.useState(() => { try { return new URLSearchParams(location.search).get('view') === 'status' ? 'installed' : 'home'; } catch { return 'home'; } }); // home | installed (situação)
  const [health, setHealth]     = React.useState(null);
  const [healthLoading, setHealthLoading] = React.useState(false);
  const loadHealth = async () => {
    setHealthLoading(true);
    try {
      const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
      const r = await fetch('/api/integrations?health=1', { cache: 'no-store', headers: { Authorization: `Bearer ${tok}` } });
      if (r.ok) setHealth(await r.json());
    } catch {}
    setHealthLoading(false);
  };
  const [detail, setDetail]     = React.useState(null);   // app aberto no painel
  const [detailTab, setDetailTab] = React.useState('desc'); // aba inicial da janela ("Gerenciar" abre em Instalação)
  const openApp = (a, t = 'desc') => { setDetailTab(t); setDetail(a); };
  const [banner, setBanner]     = React.useState(() => { try { return localStorage.getItem('horos-int-banner') !== 'off'; } catch { return true; } });
  const resultsRef = React.useRef(null);

  // Catálogo por ramo (2026-10-12, pedido do usuário: "cada ramo deve ter integrações diferentes") —
  // esconde da descoberta (recomendados/planejados/metas/busca) o que não serve pro ramo do workspace;
  // o que já está conectado nunca some (ver `installed`, que sempre lê INT_APPS inteiro).
  const segment = window.useHorosSegment ? window.useHorosSegment() : 'imobiliario';
  const [showAllSegments, setShowAllSegments] = React.useState(false);
  const visibleApps = showAllSegments ? INT_APPS : React.useMemo(() => INT_APPS.filter(a => appInSegment(a, segment)), [segment]);
  const segLabel = (window.HOROS_SEGMENTS && window.HOROS_SEGMENTS[segment] && window.HOROS_SEGMENTS[segment].label) || 'Imobiliário';

  const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(''), 3000); };

  const loadMeta = async () => {
    setLoadingMeta(true);
    try {
      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      const r = await fetch('/api/meta-connect', { headers: { 'Authorization': `Bearer ${session.access_token}` } });
      const d = await r.json();
      setMetaConns(Array.isArray(d) ? d : []);
    } catch { setMetaConns([]); }
    setLoadingMeta(false);
  };

  const loadWa = async () => {
    try {
      const uid = (window.horosActUid && window.horosActUid()) || (window.HOROS_USER || {}).id;
      const { data } = await window.HOROS_SB.from('wa_instances').select('status, phone, provider').eq('user_id', uid).maybeSingle();
      const base = { loading:false, status: data?.status || null, phone: data?.phone || null, provider: data?.provider || null };
      setWa(base);
      if (data?.provider === 'cloud') {
        const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
        const c = await fetch('/api/whatsapp-cloud', { headers: { Authorization: `Bearer ${tok}` } }).then(r => r.json()).catch(() => null);
        if (c) setWa({ ...base, verifiedName: c.verifiedName || null, quality: c.quality || null, wabaId: c.wabaId || null, tokenExpired: !!c.tokenExpired });
      }
    } catch { setWa({ loading:false, status:null, phone:null, provider:null }); }
  };

  const disconnectMeta = async (conn) => {
    setDisconnecting(conn.id);
    setMetaConns(prev => prev.filter(c => c.id !== conn.id));
    try {
      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      const res = await fetch('/api/meta-connect', {
        method: 'DELETE',
        headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${session.access_token}` },
        body: JSON.stringify({ page_id: conn.page_id }),
      });
      if (!res.ok) throw new Error();
      showToast('Página desconectada.');
    } catch {
      setMetaConns(prev => [...prev, conn]);
      showToast('Erro ao desconectar. Tente novamente.');
    }
    setDisconnecting(null);
  };

  const loadGoogle = async () => {
    try {
      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      if (!session) { setGoogleLoading(false); return; }
      const r = await fetch('/api/google-calendar?oauth', { headers: { 'Authorization': `Bearer ${session.access_token}` } });
      const d = await r.json();
      if (d.connected) { setGoogleConnected(true); setGoogleEmail(d.email); }
    } catch {}
    setGoogleLoading(false);
  };

  const handleGoogleConnect = async () => {
    setGoogleLoading(true);
    try {
      const popup = await openGoogleOAuth();
      if (!popup) { setGoogleLoading(false); return; }
      await new Promise((resolve) => {
        const handler = async (event) => {
          if (event.origin !== 'https://horos.sale') return;
          if (!event.data?.code) return;
          window.removeEventListener('message', handler);
          clearInterval(closedCheck);
          try {
            const { data: { session } } = await window.HOROS_SB.auth.getSession();
            const r = await fetch('/api/google-calendar?oauth', {
              method: 'POST',
              headers: { 'Authorization': `Bearer ${session.access_token}`, 'Content-Type': 'application/json' },
              body: JSON.stringify({ code: event.data.code }),
            });
            const d = await r.json();
            if (d.ok || d.connected) { setGoogleConnected(true); setGoogleEmail(d.email); showToast('Google Agenda conectado!'); }
            else showToast(d.message || 'Erro ao conectar Google Agenda.');
          } catch { showToast('Erro ao conectar Google Agenda.'); }
          resolve();
        };
        window.addEventListener('message', handler);
        const closedCheck = setInterval(() => {
          if (popup.closed) { clearInterval(closedCheck); window.removeEventListener('message', handler); resolve(); }
        }, 800);
      });
    } catch { showToast('Erro ao abrir janela do Google.'); }
    setGoogleLoading(false);
  };

  const handleGoogleDisconnect = async () => {
    try {
      const { data: { session } } = await window.HOROS_SB.auth.getSession();
      await fetch('/api/google-calendar?oauth', { method: 'DELETE', headers: { 'Authorization': `Bearer ${session.access_token}` } });
    } catch {}
    setGoogleConnected(false); setGoogleEmail(null);
    showToast('Google Agenda desconectado.');
  };

  const disconnectAllMeta = async () => {
    for (const conn of [...metaConns]) await disconnectMeta(conn);
  };

  // Mesmo fluxo do Desconectar de Mensagens (DELETE /api/whatsapp-instance faz logout na Evolution)
  const disconnectWa = async () => {
    try {
      const tok = window.horosToken ? await window.horosToken() : window.HOROS_SESSION?.access_token;
      const r = await fetch(wa.provider === 'cloud' ? '/api/whatsapp-cloud' : '/api/whatsapp-instance', { method:'DELETE', headers: { 'Authorization': `Bearer ${tok}` } });
      if (!r.ok) throw new Error();
      try { localStorage.setItem('horos-wa-user-disconnected', '1'); localStorage.removeItem('horos-wa-last-sync'); } catch {}
      setWa({ loading:false, status:'disconnected', phone:null, provider:null });
      showToast('WhatsApp desconectado.');
    } catch { showToast('Erro ao desconectar o WhatsApp. Tente novamente.'); }
  };

  const [gmail, setGmail] = React.useState(null);   // null = carregando · { connected, email }
  React.useEffect(() => { window.horosGmailCall && window.horosGmailCall().then(setGmail).catch(() => setGmail({ connected: false })); }, []);

  const loadOpen = async () => {
    try { setOpenSt(await intOpenCall()); } catch { setOpenSt(null); }
    setOpenLoading(false);
  };

  React.useEffect(() => { loadMeta(); loadGoogle(); loadWa(); loadOpen(); loadHealth(); }, []);
  const healthAlerts = (health?.items || []).filter(i => i.status !== 'ok').length;

  // ?app=api (aviso de webhook desligado, links da página Sobre) abre a janela já na Instalação
  React.useEffect(() => {
    try {
      const id = new URLSearchParams(location.search).get('app');
      const a = INT_APPS.find(x => x.id === id);
      if (a) openApp(a, a.kind === 'soon' ? 'desc' : 'setup');
    } catch {}
  }, []);

  React.useEffect(() => {
    if (!detail) return;
    const onKey = (e) => { if (e.key === 'Escape') setDetail(null); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [detail]);

  // Resumo do que está ligado em cada integração aberta (null = nada configurado)
  const openSummary = (app) => {
    const st = openSt; if (!st) return null;
    const keys = st.keys || [], hooks = st.webhooks || [];
    const n = (x, a, b) => `${x} ${x === 1 ? a : b}`;
    if (app.id === 'gads') { const k = keys.filter(k => k.kind === 'gads'); return k.length ? n(k.reduce((s, x) => s + (x.leads_count || 0), 0), 'lead recebido', 'leads recebidos') : null; }
    if (app.id === 'zapier' || app.id === 'make') { const k = keys.filter(k => k.kind === app.id); const h = hooks.filter(h => h.source === app.id); return (k.length || h.length) ? [k.length && n(k.length, 'chave', 'chaves'), h.length && n(h.length, 'webhook', 'webhooks')].filter(Boolean).join(' · ') : null; }
    if (app.id === 'api') { const k = keys.filter(k => k.kind === 'api'); const h = hooks.filter(h => h.source === 'manual' || h.source === 'api'); return (k.length || h.length) ? [k.length && n(k.length, 'chave', 'chaves'), h.length && n(h.length, 'webhook', 'webhooks')].filter(Boolean).join(' · ') : null; }
    if (app.kind === 'portal_email') { const c = st.portal_email?.by_portal?.[app.id]?.count || 0; return c ? n(c, 'lead recebido', 'leads recebidos') : null; }
    if (app.kind === 'properties') { const p = st.properties?.[app.id]; return p ? (app.id === 'cvcrm' ? n(p.available || 0, 'empreendimento', 'empreendimentos') : n(p.available || 0, 'imóvel', 'imóveis')) : null; }
    if (app.kind === 'api4com') return st.api4com ? (st.api4com.my_extension ? `Seu ramal: ${st.api4com.my_extension}` : 'Conta conectada · informe seu ramal') : null;
    if (app.kind === 'gads_conv') return st.gads_conv?.enabled ? `${st.gads_conv.sent || 0} conversões enviadas` : null;
    return null;
  };

  const statusOf = (app) => {
    if (app.kind === 'soon') return 'soon';
    if (app.kind === 'meta')     return loadingMeta ? 'loading' : (metaConns.length ? 'connected' : 'available');
    if (app.kind === 'google')   return googleLoading && !googleConnected ? 'loading' : (googleConnected ? 'connected' : 'available');
    if (app.kind === 'whatsapp') return wa.loading ? 'loading' : (wa.status === 'connected' ? 'connected' : 'available');
    if (app.kind === 'open')     return openLoading && !openSt ? 'loading' : (openSummary(app) ? 'connected' : 'available');
    if (app.kind === 'partner')  return openLoading && !openSt ? 'loading' : (openSt?.partners?.[app.id] ? 'connected' : 'available');
    if (app.kind === 'portal_email') return openLoading && !openSt ? 'loading' : (openSummary(app) ? 'connected' : 'available');
    if (app.kind === 'gmail') return !gmail ? 'loading' : (gmail.connected ? 'connected' : 'available');
    if (['properties', 'api4com', 'gads_conv'].includes(app.kind)) return openLoading && !openSt ? 'loading' : (openSummary(app) ? 'connected' : 'available');
    return 'available';
  };

  const byId = (id) => INT_APPS.find(a => a.id === id);
  const installed = INT_APPS.filter(a => statusOf(a) === 'connected');

  const filtered = React.useMemo(() => {
    const q = _intNorm(search).trim();
    if (!q && !goal) return null;
    const words = q.split(/\s+/).filter(Boolean);
    return visibleApps.filter(a => {
      if (goal && !(a.goals || []).includes(goal)) return false;
      if (!words.length) return true;
      const hay = _intNorm([a.name, a.by, a.desc, a.keywords, (a.features || []).join(' ')].join(' '));
      return words.every(w => hay.includes(w));
    });
  }, [search, goal, visibleApps]);

  const runSearch = (e) => {
    e && e.preventDefault();
    setSearch(query); setView('home');
    requestAnimationFrame(() => resultsRef.current?.scrollIntoView({ behavior:'smooth', block:'start' }));
  };
  const pickGoal = (id) => {
    setGoal(g => g === id ? null : id); setView('home');
    requestAnimationFrame(() => resultsRef.current?.scrollIntoView({ behavior:'smooth', block:'start' }));
  };
  const clearFilters = () => { setGoal(null); setSearch(''); setQuery(''); };
  const closeBanner = () => { setBanner(false); try { localStorage.setItem('horos-int-banner', 'off'); } catch {} };

  const goalLabel = goal ? INT_GOALS.find(g => g.id === goal)?.label : null;

  return (
    <div className="int-page">

      {/* ── Topo: busca ─────────────────────────────────────────────────────── */}
      <section className="int-hero">
        <button className="int-manage-btn" onClick={() => { if (view !== 'installed') loadHealth(); setView(v => v === 'installed' ? 'home' : 'installed'); clearFilters(); }}
                title={healthAlerts ? `${healthAlerts} ${healthAlerts === 1 ? 'integração precisa' : 'integrações precisam'} de atenção` : undefined}>
          <Icon name={view === 'installed' ? 'arrLeft' : 'gridView'} size={15} />
          {view === 'installed' ? 'Voltar ao catálogo' : 'Situação das integrações'}
          {view !== 'installed' && healthAlerts > 0 && <span className="int-manage-alert">{healthAlerts}</span>}
        </button>
        <h1 className="int-hero-title">Pesquise e conecte seus aplicativos favoritos</h1>
        <p className="int-hero-sub">Corretores com pelo menos uma integração ativa recebem os leads <b>na hora</b>, sem digitar nada.</p>
        <form className="int-search" onSubmit={runSearch}>
          <input value={query} onChange={e => setQuery(e.target.value)}
            placeholder="Digite um nome, uma palavra-chave ou a descrição do aplicativo" />
          <button type="submit" className="int-search-btn">Buscar</button>
        </form>
      </section>

      {view === 'installed' ? (
        <IntHealthView apps={installed} health={health} loading={healthLoading} onReload={loadHealth}
                       summaryOf={(a) => a.kind === 'meta' ? `${metaConns.length} página${metaConns.length > 1 ? 's' : ''} conectada${metaConns.length > 1 ? 's' : ''}`
                         : a.kind === 'google' ? (googleEmail || 'Conta Google conectada')
                         : a.kind === 'whatsapp' ? (wa.phone ? fmtPhone(String(wa.phone).replace(/^55/, '')) : 'Número conectado')
                         : a.kind === 'gmail' ? (gmail?.email || 'Gmail conectado')
                         : a.kind === 'partner' ? (openSt?.partners?.[a.id]?.account_name || 'Conta conectada')
                         : openSummary(a)}
                       onOpen={(a) => openApp(a, 'setup')} />
      ) : (
        <>
          {/* ── Aviso ────────────────────────────────────────────────────────── */}
          {banner && !filtered && (
            <section className="int-banner">
              <div style={{ flex:1, minWidth:0 }}>
                <div className="int-banner-title">O WhatsApp no Horos já está disponível</div>
                <div className="int-banner-text">Converse com seus clientes, etiquete e mova cada conversa no funil sem sair do Horos.</div>
              </div>
              <button className="int-btn" onClick={() => openApp(byId('whatsapp'))}>Experimente</button>
              <button className="int-banner-x" onClick={closeBanner} title="Fechar"><Icon name="close" size={16} /></button>
            </section>
          )}

          {/* ── Faixa de marcas ──────────────────────────────────────────────── */}
          {!filtered && (
            <div className="int-strip">
              {INT_STRIP.filter(id => showAllSegments || appInSegment(byId(id), segment)).map(id => <IntStripTile key={id} app={byId(id)} onOpen={a => openApp(a)} />)}
            </div>
          )}

          <div ref={resultsRef} />

          {filtered ? (
            <section className="int-section">
              <div className="int-results-head">
                <h2 className="int-h2">
                  {goalLabel || 'Resultados'}{search && <> para “{search}”</>} <span className="int-count">{filtered.length}</span>
                </h2>
                <button className="int-link" onClick={clearFilters}>Limpar</button>
              </div>
              {filtered.length === 0
                ? <div className="int-empty">Nenhum aplicativo encontrado. Tente outra palavra.</div>
                : <div className="int-grid">{filtered.map(a => <IntAppCard key={a.id} app={a} status={statusOf(a)} onOpen={a => openApp(a)} />)}</div>}
            </section>
          ) : (
            <section className="int-section">
              <div className="int-results-head">
                <h2 className="int-h2">Aplicativos recomendados para você</h2>
                <button className="int-link" onClick={() => setShowAllSegments(v => !v)}>
                  {showAllSegments ? 'Mostrar só o ramo ' + segLabel : `Mostrando para ${segLabel} · ver todos os ramos`}
                </button>
              </div>
              <div className="int-grid">
                {INT_RECOMMENDED.map(id => byId(id)).filter(a => showAllSegments || appInSegment(a, segment)).map(a => <IntAppCard key={a.id} app={a} status={statusOf(a)} onOpen={a => openApp(a)} />)}
              </div>
            </section>
          )}

          {/* ── Metas ────────────────────────────────────────────────────────── */}
          <section className="int-section">
            <h2 className="int-h2">Explore aplicativos por metas</h2>
            <div className="int-goals">
              {INT_GOALS.filter(g => showAllSegments || visibleApps.some(a => (a.goals || []).includes(g.id))).map(g => (
                <button key={g.id} className={'int-goal' + (goal === g.id ? ' on' : '')} onClick={() => pickGoal(g.id)}>
                  <span className="int-goal-ic"><IntGoalIcon id={g.id} /></span>
                  <span>{g.label}</span>
                </button>
              ))}
            </div>
          </section>

          {!filtered && (showAllSegments ? INT_APPS : visibleApps).some(a => a.kind === 'soon') && (
            <>
              <section className="int-section">
                <h2 className="int-h2">Planejados</h2>
                <div className="int-grid">
                  {(showAllSegments ? INT_APPS : visibleApps).filter(a => a.kind === 'soon').map(a => <IntAppCard key={a.id} app={a} status="soon" onOpen={a => openApp(a)} />)}
                </div>
              </section>
            </>
          )}
        </>
      )}

      <div className="int-foot">
        <a className="int-link" href="about-integrations.html">Sobre as integrações <Icon name="extLink" size={12} /></a>
        <a className="int-link" href="support.html?contato=1&assunto=parceria&page=integrations.html&titulo=Quero%20ser%20parceiro%20do%20Horos">Seja parceiro <Icon name="extLink" size={12} /></a>
      </div>

      {/* ── Janela do aplicativo ─────────────────────────────────────────────── */}
      {detail && (
        <IntAppModal
          key={detail.id + detailTab}
          initialTab={detailTab}
          disconnect={statusOf(detail) !== 'connected' ? null :
            detail.kind === 'meta' ? { what: metaConns.length > 1 ? `as ${metaConns.length} páginas do Facebook` : 'a página do Facebook',
                                       note:'Os próximos leads dos anúncios param de chegar. Os leads que já estão no Horos continuam.',
                                       run: disconnectAllMeta } :
            detail.kind === 'google' ? { what:'o Google Agenda', note:'Os compromissos param de sincronizar. Os eventos da Agenda do Horos continuam.',
                                         run: handleGoogleDisconnect } :
            detail.kind === 'whatsapp' ? { what:'o WhatsApp', note:'As mensagens novas param de chegar. As conversas que já estão no Horos continuam salvas.',
                                           run: disconnectWa } : null}
          waChooser={{ waStatus: wa.status, waProvider: wa.provider, onCloudConnected: (d) => {
            try { localStorage.removeItem('horos-wa-user-disconnected'); } catch {}
            setWa({ loading:false, status:'connected', provider:'cloud', phone: d.phone || null, verifiedName: d.verifiedName || null, quality: d.quality || null, wabaId: d.wabaId || null });
            showToast('WhatsApp oficial conectado!');
          } }}
          app={detail}
          status={statusOf(detail)}
          onClose={() => setDetail(null)}
          primary={
            detail.kind === 'meta'     ? { label: metaConns.length ? 'Conectar outra página' : 'Autorizar', onClick: () => setShowMeta(true) } :
            detail.kind === 'google'   ? (googleConnected ? { label:'Abrir Agenda', href:'calendar.html' }
                                                          : { label: googleLoading ? 'Conectando…' : 'Autorizar', onClick: handleGoogleConnect, disabled: googleLoading }) :
            detail.kind === 'whatsapp' ? (wa.status === 'connected' && wa.provider !== 'cloud' ? { label:'Abrir Mensagens', href:'messages.html' }
                                          : wa.status === 'connected' ? { label:'Trocar número', choose:true } : { label:'Conectar', choose:true }) :
            detail.kind === 'open'     ? { label:'Configurar', toTab:'setup' } :
            detail.kind === 'partner'  ? { label: openSt?.partners?.[detail.id] ? 'Ver conexão' : 'Conectar', toTab:'setup' } :
            detail.kind === 'portal_email' ? { label:'Ver endereço', toTab:'setup' } :
            detail.kind === 'gmail' ? { label: gmail?.connected ? 'Ver conexão' : 'Conectar', toTab:'setup' } :
            ['properties', 'api4com', 'gads_conv'].includes(detail.kind) ? { label: statusOf(detail) === 'connected' ? 'Ver conexão' : 'Conectar', toTab:'setup' } :
            { label:'Avise-me', href:`support.html?contato=1&assunto=integracoes&page=integrations.html&titulo=${encodeURIComponent('Quero a integração ' + detail.name)}` }
          }
          connection={
            detail.kind === 'meta' ? (<>
              <div className="int-block">
                <div className="int-block-title">Páginas conectadas <span className="int-count">{metaConns.length}</span></div>
                {loadingMeta ? <SkelList rows={2} avatar={32} pad="8px 0" />
                  : metaConns.length === 0 ? <div className="int-muted">Nenhuma página conectada ainda.</div>
                  : metaConns.map(conn => (
                    <div key={conn.id} className="int-conn-row">
                      <div className="int-conn-av">{(conn.page_name || 'P')[0].toUpperCase()}</div>
                      <div style={{ flex:1, minWidth:0 }}>
                        <div className="int-conn-name">{conn.page_name || conn.page_id}</div>
                        <div className="int-muted">{conn.leads_count || 0} leads recebidos</div>
                      </div>
                      <button className="int-btn" onClick={() => disconnectMeta(conn)} disabled={disconnecting === conn.id}>
                        {disconnecting === conn.id ? 'Removendo…' : 'Desconectar'}
                      </button>
                    </div>
                  ))}
                {metaConns.length > 0 && (
                  <div className="int-note">Se leads chegam como <b>"Lead Meta"</b> sem telefone, desconecte e reconecte a página para atualizar as permissões.</div>
                )}
              </div>
              <div className="int-sub-title">Conversões <span className="int-muted">· opcional</span></div>
              <div className="int-muted" style={{ marginBottom: 8 }}>Manda visitas, créditos aprovados e vendas dos leads dos anúncios de volta para o Facebook otimizar as campanhas.</div>
              <IntOpenPanel app={{ id:'meta-capi' }} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            </>) : detail.kind === 'google' ? (
              <div className="int-block">
                <div className="int-block-title">Sua conta</div>
                {googleConnected ? (
                  <div className="int-conn-row">
                    <div style={{ flex:1, minWidth:0 }} className="int-conn-name">{googleEmail || 'Conta Google'}</div>
                    <button className="int-btn" onClick={handleGoogleDisconnect}>Desconectar</button>
                  </div>
                ) : <div className="int-muted">Nenhuma conta Google conectada ainda.</div>}
              </div>
            ) : detail.kind === 'whatsapp' ? (
              <div className="int-block">
                <div className="int-block-title">Seu número</div>
                <div className="int-muted">
                  {wa.loading ? <Skel w={220} h={12} /> : wa.status === 'connected' && wa.provider === 'cloud'
                    ? <>Conectado pela API oficial <IntTag tag="oficial" />{wa.phone ? <> · <b style={{ color:'var(--fg)' }}>{fmtPhone(String(wa.phone).replace(/^55/, ''))}</b></> : null}{wa.verifiedName ? <> · {wa.verifiedName}</> : null}</>
                    : wa.status === 'connected'
                    ? <>Conectado pelo QR code <IntTag tag="nao-oficial" />{wa.phone ? <> · <b style={{ color:'var(--fg)' }}>{fmtPhone(String(wa.phone).replace(/^55/, ''))}</b></> : null}</>
                    : 'Nenhum WhatsApp conectado ainda.'}
                </div>
                {wa.provider === 'cloud' && wa.status === 'connected' && (
                  <>
                    {wa.tokenExpired && <div className="int-wa-err">A autorização do Meta venceu. Clique em Trocar número e conecte de novo.</div>}
                    {wa.quality && <div className="int-muted" style={{ marginTop: 6 }}>Qualidade do número no Meta: <b style={{ color: wa.quality === 'GREEN' ? 'var(--success)' : wa.quality === 'RED' ? 'var(--danger)' : '#d97706' }}>{({ GREEN:'alta', YELLOW:'média', RED:'baixa' })[wa.quality] || wa.quality}</b></div>}
                    <div className="int-res" style={{ marginTop: 10 }}>
                      <a href={`https://business.facebook.com/wa/manage/message-templates/${wa.wabaId ? `?waba_id=${wa.wabaId}` : ''}`} target="_blank" rel="noopener noreferrer"><Icon name="extLink" size={15} /> Criar e ver modelos de mensagem</a>
                      <a href="https://business.facebook.com/wa/manage/home/" target="_blank" rel="noopener noreferrer"><Icon name="extLink" size={15} /> Forma de pagamento no WhatsApp Manager</a>
                    </div>
                  </>
                )}
              </div>
            ) : detail.kind === 'open' ? (
              <IntOpenPanel app={detail} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            ) : detail.kind === 'partner' ? (
              <IntPartnerBlock app={detail} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            ) : detail.kind === 'portal_email' ? (
              <IntPortalEmailBlock app={detail} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            ) : detail.kind === 'properties' ? (
              <IntPropertiesBlock app={detail} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            ) : detail.kind === 'api4com' ? (
              <IntApi4ComBlock app={detail} st={openSt} loading={openLoading} onState={setOpenSt} showToast={showToast} />
            ) : detail.kind === 'gmail' ? (
              <IntGmailBlock app={detail} showToast={showToast} onChange={(c, email) => setGmail({ connected: c, email: email || null })} />
            ) : detail.kind === 'gads_conv' ? (
              <IntGadsConvBlock st={openSt} showToast={showToast} />
            ) : null
          }
        />
      )}

      {showMeta && <MetaConnectModal onClose={() => setShowMeta(false)} onConnected={(conn) => { setShowMeta(false); showToast(`"${conn.page_name}" conectada!`); setMetaConns(prev => [...prev, conn]); }} />}

      {toast && <div className="int-toast">{toast}</div>}
    </div>
  );
}

window.IntegracoesPage = IntegracoesPage;
