/* screen_marketing_openhouses.jsx
   Marketing · Open Houses — eventos físicos, inscrições e check-in QR.
   Shell alinhado a Briefings / Campanhas / Produção.
   Expõe window.MktOpenHousesScreen.
*/

const OH_THEME = {
  height: '100%', display: 'flex', flexDirection: 'column', minHeight: 0,
  '--bg': '#f8fafc', '--bg-elev': '#ffffff', '--bg-hover': '#f1f5f9', '--bg-sunken': '#e8edf5',
  '--border': '#dde3ef', '--border-strong': '#bec9de',
  '--text': '#112954', '--text-muted': '#4a5e7a', '--text-dim': '#6b7fa3',
  '--ai-500': '#3859D0',
};

const OH_STATUS_CFG = {
  planeamento: { label: 'Planeamento', color: 'var(--ai-500)', bg: 'color-mix(in oklch, var(--ai-500) 12%, transparent)' },
  confirmado:  { label: 'Confirmado',  color: 'var(--success, #22c55e)', bg: 'color-mix(in oklch, var(--success, #22c55e) 12%, transparent)' },
  realizado:   { label: 'Realizado',   color: 'var(--text-dim)', bg: 'var(--bg-sunken)' },
  cancelado:   { label: 'Cancelado',   color: 'var(--danger, #dc2626)', bg: 'color-mix(in oklch, var(--danger, #dc2626) 12%, transparent)' },
  arquivado:   { label: 'Arquivado',   color: '#64748b', bg: '#f1f5f9' },
};

const OH_REG_STATUS_CFG = {
  inscrito:     { label: 'Inscrito',       color: 'var(--ai-500)', bg: 'color-mix(in oklch, var(--ai-500) 12%, transparent)' },
  confirmado:   { label: 'Confirmado',     color: 'var(--success, #22c55e)', bg: 'color-mix(in oklch, var(--success, #22c55e) 12%, transparent)' },
  presente:     { label: 'Presente',       color: 'var(--success, #22c55e)', bg: 'color-mix(in oklch, var(--success, #22c55e) 18%, transparent)' },
  no_show:      { label: 'No-show',        color: 'var(--warning, #d97706)', bg: 'color-mix(in oklch, var(--warning, #d97706) 12%, transparent)' },
  lista_espera: { label: 'Lista de espera', color: 'var(--text-dim)', bg: 'var(--bg-sunken)' },
};

const OH_SHOWROOM_OPTS = [
  ['porto', 'Famalicão'],
  ['lisboa', 'Prior Velho'],
  ['torres_novas', 'Torres Novas'],
  ['madrid', 'Móstoles'],
  ['outro', 'Outro'],
];
const OH_SHOWROOM_LABEL = Object.fromEntries(OH_SHOWROOM_OPTS);

const OH_MARCA_OPTS = [
  ['mimaki', 'Mimaki'],
  ['decal', 'Decal'],
  ['biond', 'BIOND'],
  ['alldecor', 'AllDecor'],
  ['sensek', 'Sensek'],
  ['netscreen', 'NetScreen'],
  ['digidelta', 'Digidelta'],
];
const OH_MARCA_LABEL = Object.fromEntries(OH_MARCA_OPTS);
const OH_MARCA_COLOR = { mimaki: '#0066CC', decal: '#00A86B', biond: '#E8A000', alldecor: '#7C3AED', sensek: '#7209b7', netscreen: '#118ab2', digidelta: '#112954' };
const ohBrandColor = (slug) => {
  const map = { mimaki: '#0066cc', biond: '#4a7c2e', decal: '#e63946', alldecor: '#f77f00', sensek: '#7209b7', netscreen: '#118ab2', digidelta: '#112954' };
  return map[(slug || '').toLowerCase()] || '#3859D0';
};

const OH_TIPO_OPTS = [
  ['end_user', 'End user'],
  ['partner', 'Partner'],
  ['ambos', 'Ambos'],
];
const OH_ORG_OPTS = [
  ['digidelta', 'Digidelta'],
  ['partner', 'Partner'],
  ['co_organizado', 'Co-organizado'],
];
const OH_FONTE_LABEL = {
  manual: 'Manual', landing_page: 'Landing', typeform: 'Typeform', meta_ads: 'Meta Ads',
  email: 'Email', digi_ai: 'Digi AI', social: 'Social', gestor_import: 'Gestor',
};
const OH_CANAL_CFG = {
  email: { label: 'Email Marketing', accent: '#0891B2', bg: '#E0F7FB' },
  social: { label: 'Social Media', accent: '#059669', bg: '#ECFDF5' },
  meta_ads: { label: 'Meta Ads', accent: '#7C3AED', bg: '#F3EEF8' },
  landing: { label: 'Landing + Typeform', accent: '#0E7490', bg: '#E0F7FB' },
  digi_ai: { label: 'Digi AI SDR', accent: '#D97706', bg: '#FEF3C7' },
};
const OH_PECA_STATUS = {
  pendente: { label: 'Pendente', color: '#64748b', bg: '#f1f5f9' },
  gerado: { label: 'Draft', color: '#b45309', bg: '#FEF3C7' },
  aprovado: { label: 'Aprovado', color: '#15803d', bg: '#DCFCE7' },
  publicado: { label: 'Publicado', color: '#0E7490', bg: '#E0F7FB' },
};

const OH_ANOS = [2025, 2026, 2027];

const OH_INPUT = {
  fontSize: 12, padding: '7px 10px', borderRadius: 6,
  background: 'var(--bg-elev)', border: '1px solid var(--border)',
  color: 'var(--text)', width: '100%', boxSizing: 'border-box', fontFamily: 'inherit',
};
const OH_LABEL = {
  fontSize: 10, fontWeight: 600, color: 'var(--text-muted)',
  textTransform: 'uppercase', letterSpacing: '0.06em', display: 'block', marginBottom: 4,
  fontFamily: 'var(--font-mono)',
};
const OH_TH = {
  textAlign: 'left', padding: '8px 12px',
  fontSize: 10, fontFamily: 'var(--font-display)', fontWeight: 600,
  letterSpacing: '0.10em', textTransform: 'uppercase',
  color: 'var(--text-muted)', whiteSpace: 'nowrap',
};
const OH_TD = { padding: '10px 12px', overflow: 'hidden' };

function ohFmtDate(iso) {
  if (!iso) return '—';
  if (iso instanceof Date && !Number.isNaN(iso.getTime())) {
    const y = iso.getUTCFullYear();
    const m = String(iso.getUTCMonth() + 1).padStart(2, '0');
    const day = String(iso.getUTCDate()).padStart(2, '0');
    return `${day}/${m}/${y}`;
  }
  const s = String(iso);
  const hit = s.match(/(\d{4})-(\d{2})-(\d{2})/);
  if (hit) return `${hit[3]}/${hit[2]}/${hit[1]}`;
  return s.slice(0, 10);
}

function ohYearOf(iso) {
  const s = String(iso || '');
  const hit = s.match(/(\d{4})-/);
  return hit ? hit[1] : '';
}

function ohApi(url, options) {
  return campApiCall(url, options);
}

function ohPatchReg(eventId, regId, body) {
  return ohApi(`/api/marketing/openhouses/${eventId}/registrations/${regId}`, {
    method: 'PATCH', body: JSON.stringify(body),
  });
}

const OHBadge = ({ status, map }) => {
  const cfg = map[status] || { label: status || '—', color: 'var(--text-dim)', bg: 'var(--bg-sunken)' };
  return (
    <span style={{
      fontSize: 10, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase',
      padding: '2px 8px', borderRadius: 4, background: cfg.bg, color: cfg.color,
      fontFamily: 'var(--font-mono)', display: 'inline-block', whiteSpace: 'nowrap',
    }}>{cfg.label}</span>
  );
};

const OHDropdown = ({ label, value, options, onChange }) => {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onClick = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onClick);
    return () => document.removeEventListener('mousedown', onClick);
  }, [open]);
  const current = options.find(o => o.value === value) || options[0];
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen(v => !v)} style={{
        background: 'var(--bg-elev)', color: 'var(--text)',
        border: '1px solid var(--border)', borderRadius: 6,
        padding: '6px 10px', fontSize: 11.5,
        display: 'inline-flex', alignItems: 'center', gap: 8,
        cursor: 'pointer', fontFamily: 'inherit',
      }}>
        <span style={{ color: 'var(--text-dim)', fontSize: 10, textTransform: 'uppercase', letterSpacing: '.06em', fontFamily: 'var(--font-mono)' }}>{label}</span>
        <span style={{ fontWeight: 500 }}>{current?.label || '—'}</span>
        <span style={{ color: 'var(--text-muted)', fontSize: 9 }}>▾</span>
      </button>
      {open && (
        <div className="animate-in" style={{
          position: 'absolute', top: 'calc(100% + 4px)', left: 0, zIndex: 50,
          background: 'var(--bg-elev)', border: '1px solid var(--border)', borderRadius: 6,
          boxShadow: '0 8px 24px rgba(17,41,84,0.12)', minWidth: 180, padding: 4, maxHeight: 280, overflowY: 'auto',
        }}>
          {options.map(o => (
            <button key={o.value} type="button" onClick={() => { onChange(o.value); setOpen(false); }} style={{
              display: 'block', width: '100%', textAlign: 'left',
              background: o.value === value ? 'color-mix(in oklch, var(--ai-500) 12%, transparent)' : 'transparent',
              color: o.value === value ? 'var(--ai-500)' : 'var(--text)',
              border: 'none', padding: '7px 10px', borderRadius: 4,
              fontSize: 12, fontWeight: o.value === value ? 600 : 500,
              cursor: 'pointer', fontFamily: 'inherit',
            }}>{o.label}</button>
          ))}
        </div>
      )}
    </div>
  );
};

const OHSelect = ({ value, onChange, options, placeholder = 'Seleccionar...' }) => (
  <select value={value || ''} onChange={e => onChange(e.target.value)}
    style={{ ...OH_INPUT, color: value ? 'var(--text)' : 'var(--text-dim)', appearance: 'none' }}>
    <option value="">{placeholder}</option>
    {options.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
  </select>
);

const OHMarcaChips = ({ selected, onToggle }) => (
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
    {OH_MARCA_OPTS.map(([v, l]) => {
      const on = (selected || []).includes(v);
      const c = OH_MARCA_COLOR[v] || 'var(--ai-500)';
      return (
        <button key={v} type="button" onClick={() => onToggle(v)}
          style={{
            fontSize: 11, padding: '4px 10px', borderRadius: 99, cursor: 'pointer', fontWeight: 600,
            background: on ? c + '18' : 'var(--bg-elev)',
            border: on ? `1px solid ${c}40` : '1px solid var(--border)',
            color: on ? c : 'var(--text-muted)', fontFamily: 'var(--font-body)',
          }}>{l}</button>
      );
    })}
  </div>
);

const OHMarcaPills = ({ marcas }) => {
  const list = Array.isArray(marcas) ? marcas : [];
  if (!list.length) return null;
  return (
    <span style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
      {list.map(m => {
        const c = OH_MARCA_COLOR[m] || 'var(--text-muted)';
        return (
          <span key={m} style={{
            fontSize: 10, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase',
            padding: '2px 8px', borderRadius: 4, fontFamily: 'var(--font-mono)',
            background: c + '15', color: c, border: `1px solid ${c}25`,
          }}>{OH_MARCA_LABEL[m] || m}</span>
        );
      })}
    </span>
  );
};

const OHKpiStrip = ({ items, onClick }) => (
  <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
    {items.map((k, i) => {
      const isActive = !!k.active;
      return (
        <div key={k.label} onClick={() => onClick && onClick(k)}
          style={{
            flex: '1 1 120px', minWidth: 120, background: 'var(--bg-elev)',
            border: `1px solid ${isActive ? k.accent : 'var(--border)'}`,
            borderRadius: 8, padding: '12px 16px 0',
            display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden',
            cursor: onClick ? 'pointer' : 'default',
          }}>
          <div style={{
            fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase',
            color: isActive ? k.accent : 'var(--text-muted)', fontFamily: 'var(--font-mono)', whiteSpace: 'nowrap',
          }}>{k.label}</div>
          <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display)', color: k.accent, lineHeight: 1, paddingBottom: 10 }}>{k.value ?? 0}</div>
          <div style={{ height: 3, background: 'var(--bg-sunken)', overflow: 'hidden' }}>
            <div style={{ height: '100%', width: `${(k.fill || 0) * 100}%`, background: k.accent, transition: 'width 400ms ease' }} />
          </div>
        </div>
      );
    })}
  </div>
);

/** Header do módulo — mesmo chrome de Produção de Conteúdos. */
const OHChromeHeader = ({ filterMarca, onMarca, meta }) => {
  const marcasNav = [{ id: 'all', nome: 'Todas' }, ...((window.MKT_BRANDS || [
    { id: 'mimaki', nome: 'Mimaki' }, { id: 'biond', nome: 'BIOND' }, { id: 'decal', nome: 'Decal' },
    { id: 'alldecor', nome: 'AllDecor' }, { id: 'sensek', nome: 'Sensek' }, { id: 'netscreen', nome: 'NetScreen' },
  ]).filter(b => b.id !== 'todas' && b.id !== 'all'))];
  return (
    <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: '20px 40px 0', flexShrink: 0 }}>
      <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
        MARKETING · OPEN HOUSES
      </div>
      <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '-0.01em', lineHeight: 1.2 }}>
        Open Houses
      </h1>
      {meta && <div style={{ fontSize: 11, color: '#94A4C4', marginTop: 6 }}>{meta}</div>}
      <div style={{ height: 1, background: '#ECEFF5', margin: '16px -40px 0' }} />
      <div style={{ display: 'flex', gap: 0, overflowX: 'auto' }}>
        {marcasNav.map(b => {
          const active = b.id === filterMarca;
          return (
            <button key={b.id} type="button" onClick={() => onMarca(b.id)}
              style={{
                background: 'none', border: 'none', outline: 'none',
                borderBottom: `2px solid ${active ? '#3859D0' : 'transparent'}`,
                color: active ? '#283252' : '#94A4C4',
                fontSize: 13, fontWeight: active ? 600 : 500,
                fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
                padding: '10px 18px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
              }}>{b.nome || b.name}</button>
          );
        })}
      </div>
    </div>
  );
};

/** Trilho de navegação — reutiliza ResTrilho quando existir. */
const OHTrilho = ({ segmentos, nota, extra }) => {
  const Res = window.ResTrilho;
  if (Res) return <Res segmentos={segmentos} nota={nota} extra={extra} />;
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', background: 'var(--bg-elev, #fff)', borderRadius: 8, border: '1px solid var(--border)', boxShadow: '0 1px 4px rgba(40,41,61,0.04), 0 4px 16px rgba(96,97,112,0.08)', flexWrap: 'wrap', marginBottom: 18 }}>
      {segmentos.map((seg, i) => {
        const ultimo = i === segmentos.length - 1;
        return (
          <React.Fragment key={i}>
            {seg.onClick && !ultimo ? (
              <button type="button" onClick={seg.onClick} style={{ background: 'transparent', border: 0, cursor: 'pointer', padding: 0, fontSize: 12, color: 'var(--ai-500)', fontFamily: 'inherit' }}>{seg.label}</button>
            ) : (
              <span style={{ fontSize: 12, color: 'var(--text)', fontWeight: ultimo ? 700 : 400 }}>{seg.label}</span>
            )}
            {!ultimo && <span style={{ color: 'var(--text-dim)', fontSize: 12 }}>›</span>}
          </React.Fragment>
        );
      })}
      {nota}
      {extra && <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 }}>{extra}</div>}
    </div>
  );
};

const OHFiltro = (props) => {
  const Res = window.ResFiltro;
  if (Res) return <Res {...props} />;
  return <OHDropdown {...props} />;
};

const OH_KANBAN_COLS = [
  { id: 'planeamento', label: 'Planeamento', color: '#3859D0' },
  { id: 'confirmado', label: 'Confirmado', color: '#22c55e' },
  { id: 'realizado', label: 'Realizado', color: '#64748b' },
  { id: 'cancelado', label: 'Cancelado', color: '#dc2626' },
  { id: 'arquivado', label: 'Arquivado', color: '#94a3b8' },
];

function ohEventDays(iso) {
  if (!iso) return null;
  let y; let m; let d;
  if (iso instanceof Date && !Number.isNaN(iso.getTime())) {
    y = iso.getUTCFullYear(); m = iso.getUTCMonth(); d = iso.getUTCDate();
  } else {
    const hit = String(iso).match(/(\d{4})-(\d{2})-(\d{2})/);
    if (!hit) return null;
    y = Number(hit[1]); m = Number(hit[2]) - 1; d = Number(hit[3]);
  }
  const now = new Date();
  const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
  return Math.round((Date.UTC(y, m, d) - today) / 86400000);
}

const OHBanner = ({ eventos, userName, userEmail }) => {
  const raw = (userName || userEmail?.split('@')[0]?.split('.')[0] || 'Olá').split(' ')[0];
  const nome = raw.charAt(0).toUpperCase() + raw.slice(1);
  const upcoming = (eventos || [])
    .map(ev => ({ ...ev, _days: ohEventDays(ev.data_evento) }))
    .filter(ev => ev.status !== 'cancelado' && ev._days != null && ev._days >= 0)
    .sort((a, b) => a._days - b._days);
  const next = upcoming[0];
  const activos = (eventos || []).filter(ev => ev.status === 'planeamento' || ev.status === 'confirmado').length;
  let heading; let sub;
  if (next && next._days <= 7) {
    const quando = next._days === 0 ? 'é hoje' : next._days === 1 ? 'é amanhã' : `é daqui a ${next._days} dias`;
    heading = `${nome}, "${next.nome}" ${quando}.`;
    sub = `${OH_SHOWROOM_LABEL[next.showroom] || next.showroom} · ${next.n_inscritos || 0}/${next.capacidade_max || 0} inscritos.`;
  } else if (next) {
    heading = `${nome}, ${activos} Open House${activos !== 1 ? 's' : ''} em curso.`;
    sub = `A próxima é ${ohFmtDate(next.data_evento)} · ${next.nome}.`;
  } else if ((eventos || []).length) {
    heading = `${nome}, sem Open Houses à frente.`;
    sub = 'Os eventos já realizados ficam no quadro.';
  } else {
    heading = `${nome}, ainda não há Open Houses.`;
    sub = 'Cria a primeira a partir de um produto com briefing.';
  }
  return (
    <div>
      <h2 style={{ margin: 0, fontSize: 22, fontWeight: 700, letterSpacing: '-0.01em', color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>{heading}</h2>
      <p style={{ margin: '6px 0 0', fontSize: 13, color: '#283252', lineHeight: 1.65 }}>{sub}</p>
    </div>
  );
};

const OHCollapsedCol = ({ col, count, onExpand }) => (
  <div onClick={onExpand} title={`Expandir ${col.label}`} style={{
    width: 52, borderRadius: 8, cursor: 'pointer', overflow: 'hidden',
    background: '#ffffff', border: '1px solid #e2e8f0',
    display: 'flex', flexDirection: 'column', alignItems: 'center',
    padding: '10px 0 8px', gap: 6, marginBottom: 10,
  }}>
    <div style={{
      width: 24, height: 24, borderRadius: '50%',
      background: count > 0 ? col.color : 'transparent',
      border: `1.5px solid ${col.color}`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <span style={{ fontSize: 11, fontWeight: 700, color: count > 0 ? '#fff' : col.color, fontFamily: 'monospace', lineHeight: 1 }}>{count}</span>
    </div>
    <div style={{
      writingMode: 'vertical-rl', textOrientation: 'mixed', transform: 'rotate(180deg)',
      fontSize: 10, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase',
      color: col.color, fontFamily: 'var(--font-mono, monospace)', userSelect: 'none',
    }}>{col.label}</div>
  </div>
);

const OHColHeader = ({ col, count, total, onCollapse }) => (
  <div onClick={onCollapse} style={{ background: '#ffffff', border: '1px solid #e2e8f0', borderRadius: 8, marginBottom: 10, overflow: 'hidden', cursor: 'pointer' }}>
    <div style={{ padding: '10px 14px 0' }}>
      <div style={{ fontSize: 9.5, fontWeight: 600, letterSpacing: '0.10em', textTransform: 'uppercase', color: '#64748b', fontFamily: 'var(--font-mono, monospace)', marginBottom: 4 }}>{col.label}</div>
      <div style={{ fontSize: 22, fontWeight: 700, fontFamily: 'var(--font-display, Montserrat, sans-serif)', color: col.color, lineHeight: 1, paddingBottom: 8 }}>{count}</div>
      <div style={{ height: 3, background: '#f1f5f9', overflow: 'hidden' }}>
        <div style={{ height: '100%', width: `${total ? (count / total) * 100 : 0}%`, background: col.color, transition: 'width .4s' }} />
      </div>
    </div>
  </div>
);

const OHEventCard = ({ ev, onOpen, onChanged }) => {
  const marca = (ev.marcas || [])[0];
  const color = OH_MARCA_COLOR[marca] || '#3859D0';
  const cap = ev.capacidade_max || 0;
  const n = ev.n_inscritos || 0;
  const pct = cap ? Math.min((n / cap) * 100, 100) : 0;
  const days = ohEventDays(ev.data_evento);
  const when = days == null ? ohFmtDate(ev.data_evento)
    : days === 0 ? 'Hoje'
    : days === 1 ? 'Amanhã'
    : days > 1 ? `Em ${days}d · ${ohFmtDate(ev.data_evento)}`
    : ohFmtDate(ev.data_evento);
  const whenColor = days != null && days >= 0 && days <= 7 ? '#dc2626' : '#94a3b8';
  const [editing, setEditing] = React.useState(false);
  const [nome, setNome] = React.useState(ev.nome || '');
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  React.useEffect(() => { setNome(ev.nome || ''); }, [ev.nome]);

  async function patch(body) {
    setBusy(true); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${ev.id}`, { method: 'PATCH', body: JSON.stringify(body) });
      setEditing(false);
      onChanged && onChanged();
    } catch (e) { setErr(e.message || 'Não foi possível actualizar'); }
    finally { setBusy(false); }
  }
  async function eliminar() {
    if (!window.confirm(`Eliminar "${ev.nome}" e todas as inscrições?`)) return;
    setBusy(true); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${ev.id}`, { method: 'DELETE' });
      onChanged && onChanged();
    } catch (e) { setErr(e.message || 'Não foi possível eliminar'); setBusy(false); }
  }
  const Menu = window.ResMenuAccoes;
  const menuItems = [
    { id: 'abrir', label: 'Abrir ficha', onClick: () => onOpen(ev.id) },
    { id: 'nome', label: 'Editar nome', onClick: () => setEditing(true) },
    { separador: true },
    ...['planeamento', 'confirmado', 'realizado', 'cancelado'].filter(st => st !== ev.status).map(st => ({
      id: `st-${st}`,
      label: (OH_STATUS_CFG[st] || {}).label || st,
      sub: 'Mudar estado',
      onClick: () => patch({ status: st }),
    })),
    { separador: true },
    ev.status === 'arquivado'
      ? { id: 'restaurar', label: 'Restaurar', sub: 'Volta a planeamento', onClick: () => patch({ status: 'planeamento' }) }
      : { id: 'arquivar', label: 'Arquivar', sub: 'Sai do quadro activo', onClick: () => patch({ status: 'arquivado' }) },
    { id: 'eliminar', label: 'Eliminar', sub: 'Apaga o evento e as inscrições', onClick: eliminar },
  ];

  return (
    <div
      style={{ background: '#ffffff', border: '1px solid #e2e8f0', borderLeft: `3px solid ${color}`, borderRadius: 8, padding: 12, marginBottom: 8, cursor: editing ? 'default' : 'pointer', position: 'relative' }}
      onMouseEnter={e => { e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,0.06)'; }}
      onMouseLeave={e => { e.currentTarget.style.boxShadow = 'none'; }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }} onClick={e => e.stopPropagation()}>
        <div style={{ fontSize: 10, fontWeight: 600, letterSpacing: '0.08em', color, textTransform: 'uppercase', fontFamily: 'var(--font-mono, monospace)', marginBottom: 6 }}>
          {(OH_MARCA_LABEL[marca] || OH_SHOWROOM_LABEL[ev.showroom] || 'Open House').toUpperCase()}
        </div>
        {Menu ? <Menu icone items={menuItems} /> : (
          <button type="button" className="btn btn-sm" disabled={busy} onClick={() => setEditing(true)}>Editar</button>
        )}
      </div>
      {editing ? (
        <form onSubmit={e => { e.preventDefault(); if (nome.trim() && nome.trim() !== ev.nome) patch({ nome: nome.trim() }); else setEditing(false); }} onClick={e => e.stopPropagation()}>
          <input value={nome} autoFocus onChange={e => setNome(e.target.value)} style={{ ...OH_INPUT, marginBottom: 6 }} />
          <div style={{ display: 'flex', gap: 6 }}>
            <button type="submit" className="btn btn-sm btn-ai" disabled={busy}>{busy ? '…' : 'Guardar'}</button>
            <button type="button" className="btn btn-sm" onClick={() => { setNome(ev.nome || ''); setEditing(false); }}>Cancelar</button>
          </div>
        </form>
      ) : (
        <div onClick={() => onOpen(ev.id)} style={{ fontWeight: 700, fontSize: 13, color: '#283252', marginBottom: 2, lineHeight: 1.3 }}>{ev.nome}</div>
      )}
      {err && <div style={{ fontSize: 11, color: '#dc2626', marginTop: 4 }}>{err}</div>}
      <div onClick={() => !editing && onOpen(ev.id)} style={{ fontSize: 11, color: '#64748b', marginBottom: 10, marginTop: editing ? 8 : 0 }}>{OH_SHOWROOM_LABEL[ev.showroom] || ev.showroom}</div>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 2 }}>
        <span style={{ fontSize: 9, fontWeight: 700, color: '#3859D0', fontFamily: 'var(--font-mono, monospace)', letterSpacing: '0.06em' }}>INSCRITOS</span>
        <span style={{ fontSize: 9, color: '#64748b', fontFamily: 'var(--font-mono, monospace)' }}>{n}/{cap || '—'}</span>
      </div>
      <div style={{ height: 5, background: '#f1f5f9', borderRadius: 3, overflow: 'hidden', marginBottom: 8 }}>
        <div style={{ height: '100%', width: `${pct}%`, background: pct >= 100 ? '#dc2626' : '#3859D0', borderRadius: 3 }} />
      </div>
      <div style={{ fontSize: 10, fontFamily: 'var(--font-mono, monospace)', color: whenColor, fontWeight: days != null && days >= 0 && days <= 7 ? 700 : 400 }}>{when}</div>
    </div>
  );
};

// ─── Página criar / editar ────────────────────────────────────────────────────

function ohIso(v) { return v ? String(v).slice(0, 10) : ''; }
function ohHm(v) { return v ? String(v).slice(0, 5) : ''; }
function ohEachDay(a, b) {
  const start = ohIso(a);
  const end = ohIso(b) || start;
  if (!start) return [];
  const out = [];
  const d = new Date(start + 'T12:00:00');
  const last = new Date(end + 'T12:00:00');
  for (let i = 0; i < 14 && d <= last; i++) {
    out.push(d.toISOString().slice(0, 10));
    d.setDate(d.getDate() + 1);
  }
  return out.length ? out : [start];
}
function ohDefaultSlots(inicio, fim, cap) {
  const c = Number(cap) > 0 ? Math.max(8, Math.round(Number(cap) / 2)) : 20;
  return ohEachDay(inicio, fim).flatMap(dia => ([
    { data_slot: dia, periodo: 'manha', titulo: 'Sessão da Manhã', hora_inicio: '10:00', hora_fim: '12:00', capacidade: c },
    { data_slot: dia, periodo: 'tarde', titulo: 'Sessão da Tarde', hora_inicio: '14:00', hora_fim: '16:30', capacidade: c },
  ]));
}
function ohSlotTxt(s) {
  if (!s) return '—';
  const dia = ohFmtDate(s.data_slot || s.slot_dia || s.dia);
  const hi = ohHm(s.hora_inicio || s.slot_hora_inicio);
  const hf = ohHm(s.hora_fim || s.slot_hora_fim);
  return [dia, hi && hf ? `${hi}–${hf}` : ''].filter(Boolean).join(' · ');
}

const OHEventForm = ({ event, userName, userEmail, onClose, onSaved, onDeleted, embedded }) => {
  const isEdit = !!(event && event.id);
  const [nome, setNome] = React.useState(event?.nome || '');
  const [dataEvento, setDataEvento] = React.useState(ohIso(event?.data_evento));
  const [dataFim, setDataFim] = React.useState(ohIso(event?.data_fim) || ohIso(event?.data_evento));
  const [showroomId, setShowroomId] = React.useState(event?.showroom_id || '');
  const [showroom, setShowroom] = React.useState(event?.showroom || 'porto');
  const [capacidade, setCapacidade] = React.useState(event?.capacidade_max ?? 50);
  const [status, setStatus] = React.useState(event?.status || 'planeamento');
  const [descricao, setDescricao] = React.useState(event?.descricao || '');
  const [morada, setMorada] = React.useState(event?.local_morada || '');
  const [custo, setCusto] = React.useState(event?.custo_evento ?? '');
  const [orgTipo, setOrgTipo] = React.useState(event?.organizacao_tipo || 'digidelta');
  const [partnerId, setPartnerId] = React.useState(event?.partner_id || '');
  const [publico, setPublico] = React.useState(event?.publico_alvo || 'ambos');
  const [typeformUrl, setTypeformUrl] = React.useState(event?.typeform_url || '');
  const [sdrActiva, setSdrActiva] = React.useState(event?.sdr_activa !== false);
  const [slots, setSlots] = React.useState(() => (event?.slots || []).map(s => ({
    id: s.id, data_slot: ohIso(s.data_slot), periodo: s.periodo || 'manha', titulo: s.titulo || '',
    hora_inicio: ohHm(s.hora_inicio) || '10:00', hora_fim: ohHm(s.hora_fim) || '12:00', capacidade: s.capacidade || 20,
  })));
  const [equipaCodes, setEquipaCodes] = React.useState(Array.isArray(event?.equipas_codes) ? event.equipas_codes : []);
  const [comerciais, setComerciais] = React.useState((event?.equipas || []).map(e => e.cobertura_id).filter(Boolean));
  const [productIds, setProductIds] = React.useState((event?.produtos || []).map(p => Number(p.product_id)).filter(Boolean));
  const [qProd, setQProd] = React.useState('');
  const [catalogos, setCatalogos] = React.useState(null);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState('');

  React.useEffect(() => {
    ohApi('/api/marketing/openhouses/catalogos')
      .then(d => setCatalogos(d || {}))
      .catch(() => setCatalogos({ showrooms: [], produtos: [], equipas: [], partners: [] }));
  }, []);

  const showrooms = catalogos?.showrooms?.length
    ? catalogos.showrooms
    : OH_SHOWROOM_OPTS.map(([codigo, nome]) => ({ id: codigo, codigo, nome }));
  const produtosCat = catalogos?.produtos || [];
  const equipasCat = catalogos?.equipas || [];
  const partners = catalogos?.partners || [];
  const partnerSel = partners.find(p => String(p.id) === String(partnerId));

  function escolherShowroom(sr) {
    setShowroomId(sr.id && String(sr.id).includes('-') ? sr.id : '');
    setShowroom(sr.codigo || 'outro');
    const linha = [sr.morada || sr.morada_linha1, sr.codigo_postal, sr.cidade].filter(Boolean).join(', ');
    if (linha) setMorada(linha);
  }

  function toggleProduto(id) {
    setProductIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  }
  function toggleEquipa(code) {
    setEquipaCodes(prev => prev.includes(code) ? prev.filter(x => x !== code) : [...prev, code]);
  }
  function toggleComercial(id) {
    setComerciais(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  }
  function setSlot(i, patch) {
    setSlots(prev => prev.map((s, idx) => idx === i ? { ...s, ...patch } : s));
  }

  const produtosVisiveis = React.useMemo(() => {
    const q = qProd.trim().toLowerCase();
    return produtosCat
      .map(p => ({
        product_id: Number(p.product_id),
        brand_slug: p.brand_slug,
        brand_name: p.brand_name || OH_MARCA_LABEL[p.brand_slug] || p.brand_slug,
        label: p.commercial_name || p.product_name,
        category: p.product_category || p.category || '',
        aprovado: p.briefing_status === 'aprovado',
        briefing_status: p.briefing_status || '',
        usps: Array.isArray(p.usps) ? p.usps : [],
      }))
      .filter(p => p.product_id && (!q || `${p.brand_name} ${p.label} ${p.category} ${p.briefing_status || ''}`.toLowerCase().includes(q)))
      .sort((a, b) => Number(productIds.includes(b.product_id)) - Number(productIds.includes(a.product_id)) || Number(b.aprovado) - Number(a.aprovado) || a.label.localeCompare(b.label, 'pt'))
      .slice(0, 48);
  }, [produtosCat, qProd, productIds]);

  const comerciaisFlat = equipasCat.flatMap(eq => (eq.comerciais || []).map(c => ({ ...c, equipa_code: c.equipa_code || eq.equipa_code })));

  async function handleSave() {
    if (!nome.trim() || !dataEvento) {
      setError('Nome e data de início são obrigatórios.');
      return;
    }
    if (!productIds.length) {
      setError('Escolhe pelo menos um produto.');
      return;
    }
    const slotRows = (slots.length ? slots : ohDefaultSlots(dataEvento, dataFim || dataEvento, capacidade))
      .filter(s => s.data_slot && s.hora_inicio && s.hora_fim);
    const equipasBody = comerciaisFlat
      .filter(c => comerciais.includes(c.cobertura_id) || equipaCodes.includes(c.equipa_code))
      .map(c => ({
        cobertura_id: c.cobertura_id,
        equipa_code: c.equipa_code,
        comercial_nome: c.pessoa_nome,
        colaborador_email: c.colaborador_email,
        zona: c.zona,
        marca: c.marca,
        papel: 'comercial',
      }));
    setSaving(true); setError('');
    try {
      const body = {
        nome: nome.trim(),
        data_evento: dataEvento,
        data_fim: dataFim || dataEvento,
        showroom,
        showroom_id: showroomId || null,
        capacidade_max: Number(capacidade) || 50,
        status,
        descricao: descricao || null,
        local_morada: morada || null,
        custo_evento: custo === '' ? null : Number(custo),
        organizacao_tipo: orgTipo,
        partner_id: partnerId ? Number(partnerId) : null,
        partner_nome: partnerSel?.nome || null,
        publico_alvo: publico,
        equipas_codes: equipaCodes,
        sdr_activa: sdrActiva,
        produtos: productIds,
        slots: slotRows,
        equipas: equipasBody,
      };
      let saved;
      if (isEdit) {
        if (!event.comercial_email && userEmail) body.comercial_email = userEmail;
        saved = await ohApi(`/api/marketing/openhouses/${event.id}`, { method: 'PATCH', body: JSON.stringify(body) });
      } else {
        saved = await ohApi('/api/marketing/openhouses', {
          method: 'POST',
          body: JSON.stringify({ ...body, created_by: userName || null, comercial_email: userEmail || null }),
        });
      }
      onSaved(saved);
    } catch (e) {
      setError(e.message || 'Erro ao guardar');
    } finally { setSaving(false); }
  }

  async function handleDelete() {
    if (!isEdit) return;
    if (!window.confirm('Eliminar esta Open House e todas as inscrições?')) return;
    setSaving(true); setError('');
    try {
      await ohApi(`/api/marketing/openhouses/${event.id}`, { method: 'DELETE' });
      onDeleted(event.id);
    } catch (e) {
      setError(e.message || 'Erro ao eliminar');
      setSaving(false);
    }
  }

  const card = { background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 16 };
  const chip = (on) => ({
    fontSize: 12, padding: '6px 12px', borderRadius: 99, cursor: 'pointer', fontWeight: 600,
    background: on ? 'color-mix(in oklch, var(--ai-500) 12%, transparent)' : '#fff',
    border: on ? '1px solid var(--ai-500)' : '1px solid var(--border)',
    color: on ? 'var(--ai-500)' : 'var(--text-muted)', fontFamily: 'inherit',
  });
  const srActual = showrooms.find(s => String(s.id) === String(showroomId)) || showrooms.find(s => s.codigo === showroom);
  const vagas = slots.reduce((n, s) => n + (Number(s.capacidade) || 0), 0);

  const body = (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      {error && <div style={{ padding: '8px 12px', borderRadius: 6, fontSize: 12, color: '#dc2626', background: '#fef2f2', border: '1px solid #fecaca' }}>{error}</div>}
      <div style={{ background: '#fff', borderLeft: '3px solid var(--ai-500)', border: '1px solid var(--border)', borderLeftWidth: 3, borderRadius: 8, padding: '14px 16px' }}>
        <div style={{ fontSize: 9, letterSpacing: '0.1em', fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginBottom: 6 }}>PROPOSTA DO EVENTO</div>
        <div style={{ fontSize: 14, color: '#112954', lineHeight: 1.5 }}>{descricao || 'Demonstração presencial dos equipamentos seleccionados, com reserva de faixa horária e bilhete.'}</div>
        <div style={{ marginTop: 8, fontSize: 12, color: '#4a5e7a' }}>
          Público: {(OH_TIPO_OPTS.find(x => x[0] === publico) || [])[1] || publico} · Organização: {(OH_ORG_OPTS.find(x => x[0] === orgTipo) || [])[1] || orgTipo}
          {partnerSel ? ` · ${partnerSel.nome}` : ''}
        </div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12 }}>
        {[
          ['Dias & slots', `${ohEachDay(dataEvento, dataFim).length} dias · ${slots.length} faixas`, '#3859D0'],
          ['Objectivo', `${capacidade || 0} pessoas`, '#0891b2'],
          ['Equipas', `${equipaCodes.length} equipas`, '#7c3aed'],
          ['Produtos', `${productIds.length} em demo`, '#15803d'],
        ].map(([label, value, accent]) => (
          <div key={label} style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '12px 14px 0', overflow: 'hidden' }}>
            <div style={{ fontSize: 9, letterSpacing: '0.1em', textTransform: 'uppercase', color: accent, fontFamily: 'var(--font-mono)' }}>{label}</div>
            <div style={{ fontSize: 18, fontWeight: 700, color: '#112954', padding: '6px 0 10px' }}>{value}</div>
            <div style={{ height: 3, background: accent }} />
          </div>
        ))}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1.1fr 0.9fr', gap: 14, alignItems: 'start' }}>
        <div style={{ ...card, display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={OH_LABEL}>Showroom, datas e faixas horárias</div>
          <div>
            <label style={OH_LABEL}>Nome</label>
            <input value={nome} onChange={e => setNome(e.target.value)} style={OH_INPUT} placeholder="Essência Turbo 2026" />
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 120px', gap: 10 }}>
            <div><label style={OH_LABEL}>Início</label><input type="date" value={dataEvento} onChange={e => { setDataEvento(e.target.value); if (!dataFim || dataFim < e.target.value) setDataFim(e.target.value); }} style={OH_INPUT} /></div>
            <div><label style={OH_LABEL}>Fim</label><input type="date" value={dataFim} onChange={e => setDataFim(e.target.value)} style={OH_INPUT} /></div>
            <div><label style={OH_LABEL}>Objectivo</label><input type="number" min={1} value={capacidade} onChange={e => setCapacidade(e.target.value)} style={OH_INPUT} title="Objectivo de pessoas" /></div>
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
            {showrooms.map(sr => (
              <button key={sr.id || sr.codigo} type="button" onClick={() => escolherShowroom(sr)} style={chip(String(showroomId || showroom) === String(sr.id || sr.codigo) || showroom === sr.codigo)}>{sr.nome}</button>
            ))}
          </div>
          {srActual && (
            <div style={{ fontSize: 12, color: '#4a5e7a', lineHeight: 1.5 }}>
              {[srActual.morada || srActual.morada_linha1, srActual.codigo_postal, srActual.cidade, srActual.pais].filter(Boolean).join(' · ') || morada || 'Sem morada'}
              {(srActual.email_contacto || srActual.telefone_contacto) && <div>{[srActual.email_contacto, srActual.telefone_contacto].filter(Boolean).join(' · ')}</div>}
            </div>
          )}
          <div>
            <label style={OH_LABEL}>Morada no bilhete</label>
            <input value={morada} onChange={e => setMorada(e.target.value)} style={OH_INPUT} />
            <div style={{ fontSize: 11, color: '#64748b', marginTop: 6, lineHeight: 1.45 }}>
              O bilhete inclui esta morada, QR de entrada e QR para Google Maps e Waze.
              {srActual?.mapa_url && <> · <a href={srActual.mapa_url} target="_blank" rel="noopener noreferrer">Maps</a></>}
              {srActual?.waze_url && <> · <a href={srActual.waze_url} target="_blank" rel="noopener noreferrer">Waze</a></>}
            </div>
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <span style={OH_LABEL}>Faixas horárias</span>
            <button type="button" className="btn btn-sm" onClick={() => setSlots(ohDefaultSlots(dataEvento, dataFim, capacidade))}>Gerar manhã/tarde</button>
          </div>
          {slots.map((s, i) => (
            <div key={s.id || i} style={{ display: 'grid', gridTemplateColumns: '130px 1fr 78px 78px 70px 28px', gap: 6, alignItems: 'center' }}>
              <input type="date" value={s.data_slot} onChange={e => setSlot(i, { data_slot: e.target.value })} style={OH_INPUT} />
              <input value={s.titulo} onChange={e => setSlot(i, { titulo: e.target.value })} style={OH_INPUT} placeholder="Manhã" />
              <input value={s.hora_inicio} onChange={e => setSlot(i, { hora_inicio: e.target.value })} style={OH_INPUT} />
              <input value={s.hora_fim} onChange={e => setSlot(i, { hora_fim: e.target.value })} style={OH_INPUT} />
              <input type="number" min={1} value={s.capacidade} onChange={e => setSlot(i, { capacidade: Number(e.target.value) })} style={OH_INPUT} />
              <button type="button" onClick={() => setSlots(prev => prev.filter((_, idx) => idx !== i))} style={{ border: 0, background: 'none', color: '#dc2626', cursor: 'pointer' }}>×</button>
            </div>
          ))}
          <button type="button" className="btn btn-sm" onClick={() => setSlots(prev => [...prev, { data_slot: dataEvento, periodo: 'personalizado', titulo: 'Sessão', hora_inicio: '16:30', hora_fim: '18:30', capacidade: 12 }])}>+ Faixa</button>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
            {Object.entries(OH_STATUS_CFG).map(([v, cfg]) => (
              <button key={v} type="button" onClick={() => setStatus(v)} style={{ ...chip(status === v), color: status === v ? cfg.color : undefined, borderColor: status === v ? cfg.color : undefined }}>{cfg.label}</button>
            ))}
          </div>
          <textarea value={descricao} onChange={e => setDescricao(e.target.value)} rows={3} style={{ ...OH_INPUT, resize: 'vertical' }} placeholder="Proposta de valor do evento" />
          <div><label style={OH_LABEL}>Custo (€)</label><input type="number" min={0} step="0.01" value={custo} onChange={e => setCusto(e.target.value)} style={OH_INPUT} /></div>
        </div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <div style={{ ...card, display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={OH_LABEL}>Equipas, comerciais e partner</div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {OH_ORG_OPTS.map(([v, l]) => <button key={v} type="button" onClick={() => setOrgTipo(v)} style={chip(orgTipo === v)}>{l}</button>)}
            </div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              {OH_TIPO_OPTS.map(([v, l]) => <button key={v} type="button" onClick={() => setPublico(v)} style={chip(publico === v)}>{l}</button>)}
            </div>
            {orgTipo !== 'digidelta' && (
              <select value={partnerId} onChange={e => setPartnerId(e.target.value)} style={OH_INPUT}>
                <option value="">Partner</option>
                {partners.map(p => <option key={p.id} value={p.id}>{p.nome}{p.pais ? ` · ${p.pais}` : ''}</option>)}
              </select>
            )}
            <div style={{ fontSize: 11, color: '#64748b' }}>Equipas do Gestor</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
              {equipasCat.map(eq => (
                <button key={eq.equipa_code} type="button" onClick={() => toggleEquipa(eq.equipa_code)} style={chip(equipaCodes.includes(eq.equipa_code))}>{eq.equipa_nome || eq.equipa_code}</button>
              ))}
              {!equipasCat.length && <span style={{ fontSize: 12, color: '#94a3b8' }}>Sem equipas do Gestor.</span>}
            </div>
            <div style={{ maxHeight: 160, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 4 }}>
              {comerciaisFlat.filter(c => !equipaCodes.length || equipaCodes.includes(c.equipa_code)).slice(0, 40).map(c => (
                <button key={c.cobertura_id} type="button" onClick={() => toggleComercial(c.cobertura_id)} style={{ ...chip(comerciais.includes(c.cobertura_id)), borderRadius: 6, textAlign: 'left' }}>
                  {c.pessoa_nome} · {c.equipa_code}{c.zona ? ` · ${c.zona}` : ''}
                </button>
              ))}
            </div>
            <label style={{ fontSize: 12, display: 'flex', gap: 8, alignItems: 'center' }}>
              <input type="checkbox" checked={sdrActiva} onChange={e => setSdrActiva(e.target.checked)} /> Digi AI activa para qualificar e agendar
            </label>
          </div>
          <div style={{ ...card, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div style={OH_LABEL}>Produtos em demonstração</div>
            <input value={qProd} onChange={e => setQProd(e.target.value)} style={OH_INPUT} placeholder="Procurar nos briefings" />
            <div style={{ maxHeight: 280, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 4 }}>
              {produtosVisiveis.map(p => {
                const on = productIds.includes(p.product_id);
                return (
                  <button key={p.product_id} type="button" onClick={() => toggleProduto(p.product_id)} style={{
                    textAlign: 'left', padding: '8px 10px', borderRadius: 8, cursor: 'pointer', fontFamily: 'inherit',
                    background: on ? 'color-mix(in oklch, var(--ai-500) 8%, #fff)' : '#fff',
                    border: `1px solid ${on ? 'var(--ai-500)' : 'var(--border)'}`,
                  }}>
                    <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
                      <span style={{ fontSize: 13, color: '#112954' }}>{p.label}</span>
                      <span style={{ fontSize: 10, fontWeight: 700, color: ohBrandColor(p.brand_slug) }}>{p.brand_name}</span>
                    </div>
                    <div style={{ fontSize: 11, color: '#64748b', marginTop: 2 }}>{p.category}{p.briefing_status ? ` · briefing ${p.briefing_status}` : ''}{p.usps[0] ? ` · ${p.usps[0]}` : ''}</div>
                  </button>
                );
              })}
              {!produtosVisiveis.length && <div style={{ fontSize: 12, color: '#64748b' }}>{catalogos ? 'Nenhum produto com briefing nesta pesquisa.' : 'A carregar briefings…'}</div>}
            </div>
          </div>
        </div>
      </div>
    </div>
  );

  return (
    <div>
      <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        {onClose && !embedded && <button type="button" className="btn" onClick={onClose} disabled={saving} style={{ height: 30, fontSize: 12 }}>Cancelar</button>}
        {isEdit && <button className="btn" onClick={handleDelete} disabled={saving} style={{ height: 30, fontSize: 12, color: '#dc2626' }}>Eliminar</button>}
        <button className="btn btn-ai" onClick={handleSave} disabled={saving} style={{ height: 30, fontSize: 12 }}>{saving ? 'A guardar…' : (embedded ? 'Guardar ficha' : 'Guardar')}</button>
      </div>
      {body}
    </div>
  );
};

// ─── Lista ────────────────────────────────────────────────────────────────────

const OHEventList = ({ onSelect, onCreate, userName, userEmail, filterMarca, eventos, error, onReload }) => {
  window.useMktZoneLoading(eventos == null && !error);
  const [filterShowroom, setFilterShowroom] = React.useState('all');
  const [filterStatus, setFilterStatus] = React.useState('all');
  const [filterYear, setFilterYear] = React.useState('all');
  const [manualExpanded, setManualExpanded] = React.useState(new Set());
  const [busca, setBusca] = React.useState(false);

  const byBrand = React.useMemo(() => {
    const all = eventos || [];
    if (filterMarca === 'all') return all;
    return all.filter(ev => (ev.marcas || []).includes(filterMarca));
  }, [eventos, filterMarca]);

  const filtered = React.useMemo(() => {
    return byBrand.filter(ev => {
      if (filterShowroom !== 'all' && ev.showroom !== filterShowroom) return false;
      if (filterStatus !== 'all' && ev.status !== filterStatus) return false;
      if (filterYear !== 'all' && ohYearOf(ev.data_evento) !== filterYear) return false;
      return true;
    });
  }, [byBrand, filterShowroom, filterStatus, filterYear]);

  const kpi = React.useMemo(() => {
    const all = byBrand;
    const n = (st) => all.filter(e => e.status === st).length;
    return { total: all.length, planeamento: n('planeamento'), confirmado: n('confirmado'), realizado: n('realizado') };
  }, [byBrand]);

  const hasFilter = filterShowroom !== 'all' || filterStatus !== 'all' || filterYear !== 'all';
  const limparFiltros = () => { setFilterShowroom('all'); setFilterStatus('all'); setFilterYear('all'); };

  const MenuAccoes = window.ResMenuAccoes;
  const Icone = window.ResIcone;
  const Lupa = window.ResIconeLupa;
  const Busca = window.ResBusca;
  const itensBusca = (byBrand || []).map(ev => ({
    id: ev.id,
    tipo: (OH_STATUS_CFG[ev.status] || {}).label || ev.status,
    titulo: ev.nome || '—',
    detalhe: [OH_SHOWROOM_LABEL[ev.showroom] || ev.showroom, ohFmtDate(ev.data_evento), `${ev.n_inscritos || 0}/${ev.capacidade_max || 0}`].filter(Boolean).join(' · '),
    texto: (ev.marcas || []).map(m => OH_MARCA_LABEL[m] || m).join(' '),
  }));
  const menuItens = [
    { id: 'nova', label: 'Nova Open House', sub: 'Criar evento com produtos e slots', destaque: true, onClick: onCreate },
    { separador: true },
    ...(hasFilter ? [{ id: 'limpar', label: 'Limpar filtros', sub: [filterShowroom !== 'all' && 'Showroom', filterStatus !== 'all' && 'Estado', filterYear !== 'all' && 'Ano'].filter(Boolean).join(' · '), onClick: limparFiltros }] : []),
    { id: 'reload', label: 'Actualizar lista', sub: `${filtered.length} resultado${filtered.length !== 1 ? 's' : ''}`, onClick: onReload },
  ];
  const menu = MenuAccoes
    ? <MenuAccoes icone items={menuItens} onOpen={() => setBusca(false)} />
    : <button type="button" className="btn btn-ai" style={{ height: 30, padding: '0 14px', fontSize: 12 }} onClick={onCreate}>+ Nova Open House</button>;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
      {busca && Busca && (
        <Busca itens={itensBusca} placeholder="Procurar Open Houses" vazio="Nenhuma Open House neste filtro."
          onPick={(it) => onSelect(it.id)} onClose={() => setBusca(false)} />
      )}

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
          <OHFiltro label="Showroom" value={filterShowroom} onChange={setFilterShowroom} marcado={filterShowroom !== 'all'}
            options={[{ value: 'all', label: 'Todos' }, ...OH_SHOWROOM_OPTS.map(([v, l]) => ({ value: v, label: l }))]} />
          <OHFiltro label="Estado" value={filterStatus} onChange={setFilterStatus} marcado={filterStatus !== 'all'}
            options={[{ value: 'all', label: 'Todos' }, ...Object.entries(OH_STATUS_CFG).map(([k, v]) => ({ value: k, label: v.label }))]} />
          <OHFiltro label="Ano" value={filterYear} onChange={setFilterYear} marcado={filterYear !== 'all'}
            options={[{ value: 'all', label: 'Todos' }, ...OH_ANOS.map(y => ({ value: String(y), label: String(y) }))]} />
          <span style={{ fontSize: 12, color: 'var(--text-muted)', paddingLeft: 4 }}>
            {filtered.length} resultado{filtered.length !== 1 ? 's' : ''}
          </span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
          {Icone && Lupa && Busca && (
            <Icone label="Procurar Open Houses" activo={busca} onClick={() => setBusca(v => !v)}><Lupa /></Icone>
          )}
          {menu}
        </div>
      </div>

      <OHBanner eventos={byBrand} userName={userName} userEmail={userEmail} />

      {error && <div style={{ fontSize: 13, color: 'var(--danger, #dc2626)' }}>{error}</div>}
      {eventos === null ? (
        <div style={{ display: 'flex', gap: 12 }}>
          {OH_KANBAN_COLS.map(col => (
            <div key={col.id} style={{ width: 280, flexShrink: 0 }}>
              <div style={{ height: 72, borderRadius: 8, background: 'var(--bg-elev)', marginBottom: 12, opacity: 0.5 }} />
              <div style={{ height: 88, borderRadius: 8, background: 'var(--bg-elev)', opacity: 0.7 }} />
            </div>
          ))}
        </div>
      ) : (
        <div style={{ overflowX: 'auto', paddingBottom: 8 }}>
          {filtered.length === 0 && eventos.length > 0 && (
            <div style={{ padding: '0 0 8px', fontSize: 13, color: 'var(--text-muted)' }}>Sem resultados para os filtros actuais.</div>
          )}
          <div style={{ display: 'flex', gap: 10, minWidth: 'max-content', alignItems: 'flex-start' }}>
            {OH_KANBAN_COLS.map(col => {
              const cards = filtered.filter(ev => ev.status === col.id);
              const collapsed = cards.length === 0 && !manualExpanded.has(col.id);
              const colW = collapsed ? 52 : 280;
              return (
                <div key={col.id} style={{ width: colW, minWidth: colW, flexShrink: 0 }}>
                  {collapsed ? (
                    <OHCollapsedCol col={col} count={0} onExpand={() => setManualExpanded(prev => new Set(prev).add(col.id))} />
                  ) : (
                    <>
                      <OHColHeader col={col} count={cards.length} total={Math.max(kpi.total, 1)} onCollapse={() => {
                        if (!cards.length) setManualExpanded(prev => { const n = new Set(prev); n.delete(col.id); return n; });
                      }} />
                      {cards.map(ev => <OHEventCard key={ev.id} ev={ev} onOpen={onSelect} onChanged={onReload} />)}
                      {cards.length === 0 && <div style={{ padding: 12, textAlign: 'center', color: 'var(--text-dim)', fontSize: 11 }}>—</div>}
                    </>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
};

function OHKpiBar({ items, active, onPick }) {
  return (
    <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
      {items.map(k => {
        const on = active === k.id;
        return (
          <button key={k.id} type="button" onClick={() => onPick(k.id)} style={{
            flex: '1 1 140px', textAlign: 'left', background: on ? (k.bg || '#fff') : '#fff',
            border: `1px solid ${on ? k.accent : 'var(--border)'}`, borderRadius: 8, padding: '12px 14px 0',
            cursor: 'pointer', fontFamily: 'inherit', overflow: 'hidden',
          }}>
            <div style={{ fontSize: 9, fontWeight: 600, letterSpacing: '0.1em', textTransform: 'uppercase', color: k.accent, fontFamily: 'var(--font-mono)' }}>{k.label}</div>
            <div style={{ fontSize: 24, fontWeight: 700, color: '#112954', lineHeight: 1.1, padding: '6px 0 10px' }}>{k.value}</div>
            <div style={{ height: 3, background: k.accent, margin: '0 -14px' }} />
          </button>
        );
      })}
    </div>
  );
}

const OHTabConteudos = ({ eventId, evento, onEvento }) => {
  const [pecas, setPecas] = React.useState(evento?.pecas || []);
  const [canal, setCanal] = React.useState('all');
  const [openId, setOpenId] = React.useState(null);
  const [busy, setBusy] = React.useState('');
  const [err, setErr] = React.useState('');
  const [draft, setDraft] = React.useState(null);

  React.useEffect(() => { setPecas(evento?.pecas || []); }, [evento?.pecas]);

  async function reload() {
    const rows = await ohApi(`/api/marketing/openhouses/${eventId}/pecas`);
    setPecas(Array.isArray(rows) ? rows : []);
  }
  const base = pecas.filter(p => p.canal !== 'followup');
  const visiveis = base.filter(p => canal === 'all' || p.canal === canal);
  const aberta = visiveis.find(p => p.id === openId) || base.find(p => p.id === openId);
  const idx = aberta ? visiveis.findIndex(p => p.id === aberta.id) : -1;

  React.useEffect(() => {
    if (!aberta) { setDraft(null); return; }
    setDraft({
      assunto: aberta.assunto || '',
      preview_text: aberta.preview_text || '',
      headline: aberta.headline || '',
      body: aberta.body || '',
      cta: aberta.cta || '',
      cta_url: aberta.cta_url || '',
      prompt_imagem: aberta.prompt_imagem || '',
      imagem_url: aberta.imagem_url || '',
    });
  }, [aberta?.id]);

  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') setOpenId(null); }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  async function gerar(formato) {
    setBusy(formato || 'all'); setErr('');
    try {
      const body = formato ? { formato } : {};
      await ohApi(`/api/marketing/openhouses/${eventId}/pecas/gerar`, { method: 'POST', body: JSON.stringify(body) });
      await reload();
    } catch (e) { setErr(e.message || 'Erro ao gerar'); }
    finally { setBusy(''); }
  }
  async function guardar(status) {
    if (!aberta || !draft) return;
    setBusy('save'); setErr('');
    try {
      const row = await ohApi(`/api/marketing/openhouses/${eventId}/pecas/${aberta.id}`, {
        method: 'PATCH', body: JSON.stringify({ ...draft, ...(status ? { status } : {}) }),
      });
      setPecas(prev => prev.map(p => p.id === row.id ? row : p));
    } catch (e) { setErr(e.message || 'Erro ao guardar'); }
    finally { setBusy(''); }
  }
  async function publicar() {
    setBusy('pub'); setErr('');
    try {
      const r = await ohApi(`/api/marketing/openhouses/${eventId}/publicar-landing`, { method: 'POST', body: '{}' });
      if (r.event) onEvento(r.event);
      if (r.landing && r.landing.published === false && r.landing.reason) {
        setErr(r.landing.reason);
      }
    } catch (e) { setErr(e.message || 'Erro ao publicar'); }
    finally { setBusy(''); }
  }

  async function gerarLandings() {
    setBusy('gen-lp'); setErr('');
    try {
      const r = await ohApi(`/api/marketing/openhouses/${eventId}/gerar-landing`, {
        method: 'POST', body: JSON.stringify({ all: true }),
      });
      if (r.event) onEvento(r.event);
    } catch (e) { setErr(e.message || 'Erro ao gerar landings'); }
    finally { setBusy(''); }
  }

  async function gerarLandingProduto(productId) {
    setBusy(`lp-${productId}`); setErr('');
    try {
      const r = await ohApi(`/api/marketing/openhouses/${eventId}/gerar-landing`, {
        method: 'POST', body: JSON.stringify({ product_id: productId }),
      });
      if (r.event) onEvento(r.event);
    } catch (e) { setErr(e.message || 'Erro ao gerar landing'); }
    finally { setBusy(''); }
  }

  const [heroJobs, setHeroJobs] = React.useState({});

  async function refreshHero(productId) {
    try {
      const r = await ohApi(`/api/marketing/openhouses/${eventId}/hero-status?product_id=${productId}`);
      setHeroJobs(prev => ({ ...prev, [productId]: r.job }));
      return r.job;
    } catch { return null; }
  }

  async function gerarHero(productId) {
    setErr('');
    setHeroJobs(prev => ({ ...prev, [productId]: { status: 'running', message: 'A arrancar…' } }));
    try {
      const r = await ohApi(`/api/marketing/openhouses/${eventId}/gerar-hero`, {
        method: 'POST', body: JSON.stringify({ product_id: productId }),
      });
      setHeroJobs(prev => ({ ...prev, [productId]: r.job || { status: 'running', message: 'A gerar clips' } }));
    } catch (e) {
      setErr(e.message || 'Erro ao gerar o vídeo');
      setHeroJobs(prev => ({ ...prev, [productId]: { status: 'error', message: e.message } }));
    }
  }

  React.useEffect(() => {
    const running = Object.entries(heroJobs).filter(([, job]) => job && job.status === 'running');
    if (!running.length) return undefined;
    const timer = setInterval(async () => {
      for (const [id] of running) {
        const job = await refreshHero(id);
        if (job && job.status === 'error') setErr(job.message || 'O vídeo falhou');
      }
    }, 8000);
    return () => clearInterval(timer);
  }, [heroJobs, eventId]);

  const produtosOh = evento?.produtos || [];

  const counts = {
    all: base.length,
    email: base.filter(p => p.canal === 'email').length,
    social: base.filter(p => p.canal === 'social').length,
    meta_ads: base.filter(p => p.canal === 'meta_ads').length,
    landing: base.filter(p => p.canal === 'landing').length,
    digi_ai: base.filter(p => p.canal === 'digi_ai').length,
  };
  const pendentes = base.filter(p => !p.body && p.status !== 'aprovado').length;

  if (aberta && draft) {
    const cfg = OH_CANAL_CFG[aberta.canal] || { label: aberta.canal, accent: '#3859D0' };
    const isEmail = aberta.canal === 'email';
    const isDigi = aberta.canal === 'digi_ai';
    const isLanding = aberta.canal === 'landing';
    const isSocial = aberta.canal === 'social' || aberta.canal === 'meta_ads';
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '8px 12px' }}>
          <div style={{ fontSize: 12, color: '#4a5e7a' }}>
            <span style={{ background: cfg.bg, color: cfg.accent, borderRadius: 99, padding: '2px 8px', fontSize: 10, fontWeight: 700, marginRight: 8 }}>{cfg.label}</span>
            Produção de Conteúdos › {aberta.titulo}
          </div>
          <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
            <button type="button" className="btn btn-sm" disabled={idx <= 0} onClick={() => setOpenId(visiveis[idx - 1].id)}>←</button>
            <span style={{ fontSize: 12, fontFamily: 'var(--font-mono)' }}>{idx + 1} / {visiveis.length}</span>
            <button type="button" className="btn btn-sm" disabled={idx >= visiveis.length - 1} onClick={() => setOpenId(visiveis[idx + 1].id)}>→</button>
            <button type="button" className="btn btn-sm" onClick={() => setOpenId(null)}>Fechar</button>
          </div>
        </div>
        {err && <div style={{ fontSize: 12, color: '#dc2626' }}>{err}</div>}
        <div style={{ display: 'grid', gridTemplateColumns: '280px 1fr 320px', gap: 12, alignItems: 'start' }}>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: '#112954' }}>{aberta.titulo}</div>
            <div style={{ fontSize: 12, color: '#64748b', lineHeight: 1.45 }}>{aberta.subtitulo || aberta.fase}</div>
            <div style={{ fontSize: 12, color: '#4a5e7a' }}>{(evento.slots || []).map(s => ohSlotTxt(s)).join(' · ') || 'Sem slots'}</div>
            <button className="btn btn-ai" disabled={!!busy} onClick={() => gerar(aberta.formato)}>{busy === aberta.formato ? 'A gerar…' : (aberta.body ? 'Regerar' : 'Gerar')}</button>
            <button className="btn" disabled={!!busy} onClick={() => guardar()}>Guardar</button>
            <button className="btn" disabled={!!busy} onClick={() => guardar('aprovado')}>Aprovar peça</button>
            {isLanding && (
              <>
                <button className="btn btn-ai" disabled={!!busy} onClick={gerarLandings}>{busy === 'gen-lp' ? 'A gerar…' : 'Gerar landings (por máquina)'}</button>
                <button className="btn" disabled={!!busy} onClick={publicar}>{busy === 'pub' ? 'A publicar FTP…' : 'Publicar FTP (hub + máquinas)'}</button>
              </>
            )}
          </div>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14, display: 'flex', flexDirection: 'column', gap: 8 }}>
            {(isEmail || isSocial) && <div><label style={OH_LABEL}>Headline</label><input value={draft.headline} onChange={e => setDraft({ ...draft, headline: e.target.value })} style={OH_INPUT} /></div>}
            {isEmail && <div><label style={OH_LABEL}>Assunto</label><input value={draft.assunto} onChange={e => setDraft({ ...draft, assunto: e.target.value })} style={OH_INPUT} /></div>}
            {isEmail && <div><label style={OH_LABEL}>Preview</label><input value={draft.preview_text} onChange={e => setDraft({ ...draft, preview_text: e.target.value })} style={OH_INPUT} /></div>}
            <div><label style={OH_LABEL}>{isDigi ? 'Ficha SDR' : 'Copy'}</label>
              <textarea value={draft.body} onChange={e => setDraft({ ...draft, body: e.target.value })} rows={isDigi ? 18 : 12} style={{ ...OH_INPUT, resize: 'vertical', fontFamily: isDigi ? 'var(--font-mono)' : 'inherit' }} />
            </div>
            {!isDigi && <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
              <div><label style={OH_LABEL}>CTA</label><input value={draft.cta} onChange={e => setDraft({ ...draft, cta: e.target.value })} style={OH_INPUT} /></div>
              <div><label style={OH_LABEL}>URL</label><input value={draft.cta_url} onChange={e => setDraft({ ...draft, cta_url: e.target.value })} style={OH_INPUT} /></div>
            </div>}
            {isSocial && <div><label style={OH_LABEL}>Prompt visual</label><textarea value={draft.prompt_imagem} onChange={e => setDraft({ ...draft, prompt_imagem: e.target.value })} rows={4} style={{ ...OH_INPUT, resize: 'vertical' }} /></div>}
          </div>
          <div style={{ background: '#112954', color: '#fff', borderRadius: 10, padding: 14, minHeight: 280 }}>
            <div style={{ fontSize: 10, letterSpacing: '0.08em', opacity: 0.7, marginBottom: 8 }}>{isDigi ? 'WHATSAPP' : isLanding ? 'LANDING' : isEmail ? 'EMAIL' : 'FEED'}</div>
            <div style={{ fontSize: 14, fontWeight: 700, marginBottom: 8 }}>{draft.headline || draft.assunto || aberta.titulo}</div>
            <div style={{ fontSize: 12, lineHeight: 1.5, whiteSpace: 'pre-wrap', opacity: 0.92 }}>{(draft.body || 'Ainda sem copy.').slice(0, 900)}</div>
            {draft.cta && <div style={{ marginTop: 12, display: 'inline-block', background: '#3859D0', borderRadius: 6, padding: '6px 10px', fontSize: 12 }}>{draft.cta}</div>}
            {evento.landing_url && isLanding && <div style={{ marginTop: 10, fontSize: 11, wordBreak: 'break-all' }}>{evento.landing_url}</div>}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          <OHDropdown label="Canal" value={canal} onChange={setCanal} options={[
            { value: 'all', label: 'Todos os canais' },
            ...Object.entries(OH_CANAL_CFG).map(([id, c]) => ({ value: id, label: c.label })),
          ]} />
          <span style={{ fontSize: 12, color: '#64748b', alignSelf: 'center' }}>{visiveis.length} de {pecas.length} peças</span>
        </div>
        <button className="btn btn-ai" disabled={!!busy || !pendentes} onClick={() => gerar()}>{busy === 'all' ? 'A gerar…' : `Gerar pendentes (${pendentes})`}</button>
      </div>
      {err && <div style={{ fontSize: 12, color: '#dc2626' }}>{err}</div>}

      <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 10 }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 700, color: '#112954' }}>Landings de conversão (por máquina)</div>
            <div style={{ fontSize: 12, color: '#64748b', marginTop: 2 }}>
              Hub{evento?.landing_url ? <> · <a href={evento.landing_url} target="_blank" rel="noopener noreferrer">{evento.landing_url}</a></> : ' · ainda sem URL'}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            <button type="button" className="btn btn-ai" disabled={!!busy || !produtosOh.length} onClick={gerarLandings}>
              {busy === 'gen-lp' ? 'A gerar…' : 'Gerar todas'}
            </button>
            <button type="button" className="btn" disabled={!!busy || !produtosOh.length} onClick={publicar}>
              {busy === 'pub' ? 'A publicar FTP…' : 'Publicar FTP'}
            </button>
          </div>
        </div>
        {!produtosOh.length && <div style={{ fontSize: 12, color: '#94a3b8' }}>Selecciona produtos na ficha para gerar landings.</div>}
        {!!produtosOh.length && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {produtosOh.map(p => (
              <div key={p.product_id} style={{ display: 'flex', justifyContent: 'space-between', gap: 8, alignItems: 'center', padding: '8px 10px', border: '1px solid #eef2f7', borderRadius: 8 }}>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 650, color: '#112954' }}>{p.commercial_name || p.product_name}</div>
                  <div style={{ fontSize: 11, color: '#64748b', wordBreak: 'break-all' }}>
                    {p.landing_status || 'pendente'}
                    {p.landing_url ? <> · <a href={p.landing_url} target="_blank" rel="noopener noreferrer">{p.landing_url}</a></> : ''}
                  </div>
                </div>
                <div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
                  <button type="button" className="btn btn-sm" disabled={!!busy || heroJobs[p.product_id]?.status === 'running'} onClick={() => gerarHero(p.product_id)}>
                    {heroJobs[p.product_id]?.status === 'running'
                      ? (heroJobs[p.product_id].message || 'Vídeo…')
                      : (heroJobs[p.product_id]?.status === 'done' ? 'Vídeo pronto' : 'Vídeo hero')}
                  </button>
                  <button type="button" className="btn btn-sm" disabled={!!busy} onClick={() => gerarLandingProduto(p.product_id)}>
                    {busy === `lp-${p.product_id}` ? '…' : 'Gerar'}
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>

      <OHKpiBar active={canal} onPick={setCanal} items={[
        { id: 'all', label: 'Plano total', value: counts.all, accent: '#3859D0', bg: '#EFF3FF' },
        { id: 'email', label: 'Email Marketing', value: counts.email, accent: '#0891B2', bg: '#E0F7FB' },
        { id: 'social', label: 'Social Media', value: counts.social, accent: '#059669', bg: '#ECFDF5' },
        { id: 'meta_ads', label: 'Meta Ads', value: counts.meta_ads, accent: '#7C3AED', bg: '#F3EEF8' },
        { id: 'landing', label: 'Landing + Typeform', value: counts.landing, accent: '#0E7490', bg: '#E0F7FB' },
        { id: 'digi_ai', label: 'Digi AI SDR', value: counts.digi_ai, accent: '#D97706', bg: '#FEF3C7' },
      ]} />
      <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, overflow: 'auto' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12.5 }}>
          <thead><tr>{['#', 'Peça', 'Canal', 'Fase', 'Estado', ''].map(h => <th key={h} style={OH_TH}>{h}</th>)}</tr></thead>
          <tbody>
            {visiveis.map(p => {
              const st = OH_PECA_STATUS[p.status] || OH_PECA_STATUS.pendente;
              const cfg = OH_CANAL_CFG[p.canal] || { label: p.canal, accent: '#3859D0', bg: '#f8fafc' };
              return (
                <tr key={p.id} onClick={() => setOpenId(p.id)} style={{ cursor: 'pointer', borderTop: '1px solid #eef2f7' }}>
                  <td style={OH_TD}>{p.ordem}</td>
                  <td style={OH_TD}><div style={{ fontWeight: 650, color: '#112954' }}>{p.titulo}</div><div style={{ fontSize: 11, color: '#64748b' }}>{p.subtitulo}</div></td>
                  <td style={OH_TD}><span style={{ background: cfg.bg, color: cfg.accent, borderRadius: 99, padding: '2px 8px', fontSize: 10, fontWeight: 700 }}>{cfg.label}</span></td>
                  <td style={OH_TD}>{p.fase}</td>
                  <td style={OH_TD}><span style={{ background: st.bg, color: st.color, borderRadius: 99, padding: '2px 8px', fontSize: 10, fontWeight: 700 }}>{st.label}</span></td>
                  <td style={OH_TD}><button type="button" className="btn btn-sm" onClick={e => { e.stopPropagation(); gerar(p.formato); }}>{p.body ? 'Regerar' : 'Gerar'}</button></td>
                </tr>
              );
            })}
            {!visiveis.length && <tr><td colSpan={6} style={{ padding: 16, color: '#64748b' }}>Sem peças. Guarda a ficha do evento para criar o plano.</td></tr>}
          </tbody>
        </table>
      </div>
    </div>
  );
};

const OHTabInscricoes = ({ eventId, evento, regs, onRefresh }) => {
  const [q, setQ] = React.useState('');
  const [estado, setEstado] = React.useState('all');
  const [crm, setCrm] = React.useState('all');
  const [slot, setSlot] = React.useState('all');
  const [fonte, setFonte] = React.useState('all');
  const [kpi, setKpi] = React.useState('all');
  const [sel, setSel] = React.useState([]);
  const [open, setOpen] = React.useState(null);
  const [ficha, setFicha] = React.useState(null);
  const [novo, setNovo] = React.useState(false);
  const [form, setForm] = React.useState({ nome: '', empresa: '', email: '', telefone: '', nif: '' });
  const [busy, setBusy] = React.useState('');
  const [err, setErr] = React.useState('');
  const slots = evento?.slots || [];

  const rows = regs.filter(r => {
    if (q && !`${r.nome} ${r.empresa} ${r.email} ${r.nif} ${r.ticket_numero}`.toLowerCase().includes(q.toLowerCase())) return false;
    if (estado !== 'all' && r.status !== estado && r.sdr_status !== estado) return false;
    if (crm === 'cliente' && !(r.crm_match_nivel && r.crm_match_nivel !== 'novo')) return false;
    if (crm === 'novo' && r.crm_match_nivel && r.crm_match_nivel !== 'novo') return false;
    if (slot !== 'all' && String(r.slot_id) !== String(slot)) return false;
    if (fonte !== 'all' && r.fonte !== fonte) return false;
    if (kpi === 'qualifica' && r.sdr_status !== 'em_qualificacao') return false;
    if (kpi === 'bilhete' && !(r.status === 'confirmado' || r.sdr_status === 'agendado' || r.ticket_numero)) return false;
    if (kpi === 'crm' && !(r.crm_match_nivel && r.crm_match_nivel !== 'novo')) return false;
    if (kpi === 'novos' && r.crm_match_nivel && r.crm_match_nivel !== 'novo') return false;
    return true;
  });

  async function addReg(e) {
    e.preventDefault();
    setBusy('add'); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/registrations`, { method: 'POST', body: JSON.stringify({ ...form, fonte: 'manual' }) });
      setNovo(false); setForm({ nome: '', empresa: '', email: '', telefone: '', nif: '' });
      onRefresh();
    } catch (ex) { setErr(ex.message || 'Erro'); }
    finally { setBusy(''); }
  }
  async function matchAll() {
    setBusy('match'); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/registrations/match-crm`, { method: 'POST', body: '{}' });
      onRefresh();
    } catch (ex) { setErr(ex.message || 'Erro no match'); }
    finally { setBusy(''); }
  }
  async function confirmar(reg, slotId) {
    setBusy(reg.id);
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/registrations/${reg.id}/confirmar-agendamento`, {
        method: 'POST', body: JSON.stringify({ slot_id: slotId || reg.slot_id || null, enviar_email: true }),
      });
      onRefresh();
      if (open?.id === reg.id) abrir(reg.id);
    } catch (ex) { setErr(ex.message || 'Erro ao confirmar'); }
    finally { setBusy(''); }
  }
  async function abrir(id) {
    setOpen(regs.find(r => r.id === id) || { id });
    setFicha(null);
    try { setFicha(await ohApi(`/api/marketing/openhouses/${eventId}/registrations/${id}/ficha-crm`)); }
    catch (ex) { setErr(ex.message || 'Erro na ficha'); }
  }

  if (open) {
    const reg = ficha?.registration || regs.find(r => r.id === open.id) || open;
    const g = ficha?.gestor;
    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', background: '#fff', border: '1px solid var(--border)', borderRadius: 8, padding: '8px 12px' }}>
          <div style={{ fontSize: 13, color: '#112954' }}>Inscrições › {reg.empresa || 'Sem empresa'} · {reg.nome}</div>
          <button className="btn btn-sm" onClick={() => { setOpen(null); setFicha(null); }}>Fechar</button>
        </div>
        {err && <div style={{ fontSize: 12, color: '#dc2626' }}>{err}</div>}
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 12 }}>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14 }}>
            <div style={OH_LABEL}>Agendamento e bilhete</div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 13, margin: '8px 0' }}>{reg.ticket_numero || 'Sem bilhete'}</div>
            <select value={reg.slot_id || ''} onChange={e => confirmar(reg, e.target.value ? Number(e.target.value) : null)} style={{ ...OH_INPUT, marginBottom: 8 }}>
              <option value="">Faixa horária</option>
              {slots.map(s => <option key={s.id} value={s.id}>{ohSlotTxt(s)}</option>)}
            </select>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
              <button className="btn btn-ai btn-sm" disabled={busy === reg.id} onClick={() => confirmar(reg, reg.slot_id)}>Enviar bilhete</button>
              <a className="btn btn-sm" href={`/api/marketing/openhouses/${eventId}/registrations/${reg.id}/ticket.pdf`} target="_blank" rel="noreferrer">PDF</a>
              <a className="btn btn-sm" href={`/api/marketing/openhouses/${eventId}/registrations/${reg.id}/evento.ics`}>ICS</a>
            </div>
          </div>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14 }}>
            <div style={OH_LABEL}>CRM</div>
            <div style={{ marginTop: 8, fontSize: 13, color: reg.crm_match_nivel && reg.crm_match_nivel !== 'novo' ? '#15803d' : '#3859D0', fontWeight: 700 }}>
              {reg.crm_match_nivel && reg.crm_match_nivel !== 'novo' ? `Já no Gestor${reg.crm_tenant ? ` · ${reg.crm_tenant}` : ''}${reg.crm_entidade_fm_id ? ` #${reg.crm_entidade_fm_id}` : ''}` : 'Novo'}
            </div>
            <div style={{ fontSize: 12, color: '#4a5e7a', marginTop: 6 }}>{reg.crm_entidade_nome || reg.empresa || '—'} · {reg.nif || 'sem NIF'}</div>
            <div style={{ fontSize: 12, color: '#4a5e7a' }}>{reg.crm_equipa_code || '—'} · {reg.crm_comercial_atribuido || reg.comercial_responsavel || 'sem comercial'}</div>
            {g?.oportunidades && <div style={{ fontSize: 12, marginTop: 8 }}>{(g.oportunidades || []).length} oportunidades no Gestor</div>}
          </div>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 14 }}>
            <div style={OH_LABEL}>Digi AI</div>
            <div style={{ marginTop: 8, fontSize: 13, fontWeight: 650 }}>{reg.sdr_status || 'pendente'}</div>
            <div style={{ fontSize: 12, color: '#4a5e7a', marginTop: 6, whiteSpace: 'pre-wrap' }}>{reg.sdr_notas || 'Sem notas de qualificação.'}</div>
            <div style={{ fontSize: 12, color: '#64748b', marginTop: 8 }}>{reg.email || '—'} · {reg.telefone || '—'}</div>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
        <OHDropdown label="Estado" value={estado} onChange={setEstado} options={[{ value: 'all', label: 'Todos' }, ...Object.entries(OH_REG_STATUS_CFG).map(([k, v]) => ({ value: k, label: v.label })), { value: 'em_qualificacao', label: 'Em qualificação' }]} />
        <OHDropdown label="CRM" value={crm} onChange={setCrm} options={[{ value: 'all', label: 'Todos' }, { value: 'cliente', label: 'Já no Gestor' }, { value: 'novo', label: 'Novo' }]} />
        <OHDropdown label="Slot" value={slot} onChange={setSlot} options={[{ value: 'all', label: 'Todos' }, ...slots.map(s => ({ value: String(s.id), label: ohSlotTxt(s) }))]} />
        <OHDropdown label="Fonte" value={fonte} onChange={setFonte} options={[{ value: 'all', label: 'Todas' }, ...Object.entries(OH_FONTE_LABEL).map(([k, v]) => ({ value: k, label: v }))]} />
        <input value={q} onChange={e => setQ(e.target.value)} placeholder="Pesquisar" style={{ ...OH_INPUT, width: 180 }} />
        <span style={{ fontSize: 12, color: '#64748b' }}>{rows.length} de {regs.length}</span>
        <button className="btn btn-sm" onClick={() => { setEstado('all'); setCrm('all'); setSlot('all'); setFonte('all'); setQ(''); setKpi('all'); }}>Limpar</button>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
          <button className="btn btn-sm" disabled={!!busy} onClick={matchAll}>{busy === 'match' ? 'A cruzar…' : 'Match CRM'}</button>
          <button className="btn btn-ai btn-sm" onClick={() => setNovo(v => !v)}>+ Nova inscrição</button>
        </div>
      </div>
      {err && <div style={{ fontSize: 12, color: '#dc2626' }}>{err}</div>}
      <OHKpiBar active={kpi} onPick={id => setKpi(kpi === id ? 'all' : id)} items={[
        { id: 'all', label: 'Total inscritos', value: regs.length, accent: '#3859D0' },
        { id: 'qualifica', label: 'Em qualificação', value: regs.filter(r => r.sdr_status === 'em_qualificacao').length, accent: '#d97706' },
        { id: 'bilhete', label: 'Com bilhete', value: regs.filter(r => r.status === 'confirmado' || r.sdr_status === 'agendado').length, accent: '#15803d' },
        { id: 'crm', label: 'Clientes CRM', value: regs.filter(r => r.crm_match_nivel && r.crm_match_nivel !== 'novo').length, accent: '#7c3aed' },
        { id: 'novos', label: 'Novos', value: regs.filter(r => !(r.crm_match_nivel && r.crm_match_nivel !== 'novo')).length, accent: '#0891b2' },
      ]} />
      {sel.length > 0 && (
        <div style={{ background: '#EFF3FF', borderRadius: 8, padding: '8px 12px', display: 'flex', gap: 8, alignItems: 'center' }}>
          <span style={{ fontSize: 12 }}>{sel.length} seleccionados</span>
          <button className="btn btn-sm" onClick={async () => { for (const id of sel) { const r = regs.find(x => x.id === id); if (r) await confirmar(r, r.slot_id); } setSel([]); }}>Confirmar e enviar bilhete</button>
        </div>
      )}
      {novo && (
        <form onSubmit={addReg} style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr) auto', gap: 8, background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 12 }}>
          {['nome', 'empresa', 'nif', 'email', 'telefone'].map(k => <input key={k} required={k === 'nome'} placeholder={k} value={form[k]} onChange={e => setForm({ ...form, [k]: e.target.value })} style={OH_INPUT} />)}
          <button className="btn btn-ai" disabled={busy === 'add'}>Guardar</button>
        </form>
      )}
      <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, overflow: 'auto' }}>
        <table style={{ width: '100%', minWidth: 980, borderCollapse: 'collapse' }}>
          <thead><tr>{['', 'Participante', 'Empresa', 'CRM', 'Equipa', 'Slot', 'SDR', 'Bilhete', ''].map(h => <th key={h} style={OH_TH}>{h}</th>)}</tr></thead>
          <tbody>
            {rows.map(r => (
              <tr key={r.id} className="prosp-row" onClick={() => abrir(r.id)} style={{ cursor: 'pointer', borderTop: '1px solid #eef2f7' }}>
                <td style={OH_TD} onClick={e => e.stopPropagation()}><input type="checkbox" checked={sel.includes(r.id)} onChange={() => setSel(prev => prev.includes(r.id) ? prev.filter(x => x !== r.id) : [...prev, r.id])} /></td>
                <td style={OH_TD}><div style={{ fontWeight: 650 }}>{r.nome}</div><div style={{ fontSize: 11, color: '#64748b' }}>{r.cargo || ''} {r.email || r.telefone || ''}</div></td>
                <td style={OH_TD}>{r.empresa || '—'}<div style={{ fontSize: 11, color: '#64748b' }}>{r.nif || ''}</div></td>
                <td style={OH_TD}><span style={{ fontSize: 11, fontWeight: 700, color: r.crm_match_nivel && r.crm_match_nivel !== 'novo' ? '#15803d' : '#3859D0' }}>{r.crm_match_nivel && r.crm_match_nivel !== 'novo' ? `Já no Gestor${r.crm_tenant ? ` · ${r.crm_tenant}` : ''}${r.crm_entidade_fm_id ? ` #${r.crm_entidade_fm_id}` : ''}` : 'Novo'}</span></td>
                <td style={OH_TD}>{r.crm_equipa_code || r.equipa_code || '—'}<div style={{ fontSize: 11, color: '#64748b' }}>{r.crm_comercial_atribuido || r.comercial_responsavel || ''}</div></td>
                <td style={OH_TD}>{ohSlotTxt(r)}</td>
                <td style={OH_TD}>{r.sdr_status || '—'}</td>
                <td style={{ ...OH_TD, fontFamily: 'var(--font-mono)', fontSize: 11 }}>{r.ticket_numero || '—'}{r.bilhete_enviado_at ? ' · enviado' : ''}</td>
                <td style={OH_TD} onClick={e => e.stopPropagation()}>
                  <button className="btn btn-sm" onClick={() => confirmar(r, r.slot_id)}>Bilhete</button>
                </td>
              </tr>
            ))}
            {!rows.length && <tr><td colSpan={9} style={{ padding: 16, color: '#64748b' }}>Sem inscrições neste filtro.</td></tr>}
          </tbody>
        </table>
      </div>
    </div>
  );
};

const OHTabCheckin = ({ eventId, onRefresh }) => {
  const [code, setCode] = React.useState('');
  const [hit, setHit] = React.useState(null);
  const [err, setErr] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  async function go(e) {
    e?.preventDefault();
    if (!code.trim()) return;
    setBusy(true); setErr(''); setHit(null);
    try {
      const row = await ohApi(`/api/marketing/openhouses/${eventId}/checkin`, { method: 'POST', body: JSON.stringify({ ticket_numero: code.trim(), qr_code: code.trim() }) });
      setHit(row); setCode(''); onRefresh();
    } catch (ex) { setErr(ex.message || 'Bilhete não encontrado'); }
    finally { setBusy(false); }
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 720 }}>
      <form onSubmit={go} style={{ display: 'flex', gap: 8 }}>
        <input value={code} onChange={e => setCode(e.target.value)} placeholder="Nº do bilhete ou QR" style={OH_INPUT} autoFocus />
        <button className="btn btn-ai" disabled={busy}>{busy ? '…' : 'Check-in'}</button>
      </form>
      {err && <div style={{ fontSize: 12, color: '#dc2626' }}>{err}</div>}
      {hit && (
        <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 16 }}>
          <div style={{ fontSize: 18, fontWeight: 700, color: '#112954' }}>{hit.nome}</div>
          <div style={{ fontSize: 13, color: '#4a5e7a', marginTop: 4 }}>{hit.empresa || '—'} · {ohSlotTxt(hit)}</div>
          <div style={{ marginTop: 8, fontWeight: 700, color: hit.crm_match_nivel && hit.crm_match_nivel !== 'novo' ? '#15803d' : '#3859D0' }}>{hit.crm_match_nivel && hit.crm_match_nivel !== 'novo' ? 'Cliente CRM' : 'Novo'}</div>
          <div style={{ fontSize: 13, marginTop: 6 }}>Comercial no showroom: {hit.crm_comercial_atribuido || hit.comercial_responsavel || 'por atribuir'}</div>
          <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, marginTop: 8 }}>{hit.ticket_numero}</div>
        </div>
      )}
    </div>
  );
};

const OHTabDashboard = ({ eventId, evento }) => {
  const [att, setAtt] = React.useState(null);
  const [roi, setRoi] = React.useState(null);
  React.useEffect(() => {
    ohApi(`/api/marketing/openhouses/${eventId}/attendance`).then(setAtt).catch(() => {});
    ohApi(`/api/marketing/openhouses/${eventId}/roi`).then(setRoi).catch(() => {});
  }, [eventId, evento?.n_inscritos]);
  const a = att || {};
  const slots = evento?.slots || [];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <OHKpiBar active="" onPick={() => {}} items={[
        { id: 'i', label: 'Inscritos', value: a.total_inscritos || 0, accent: '#3859D0' },
        { id: 'q', label: 'Em qualificação', value: a.em_qualificacao || 0, accent: '#d97706' },
        { id: 'c', label: 'Com bilhete', value: a.confirmados || 0, accent: '#15803d' },
        { id: 'p', label: 'Presentes', value: a.presentes || 0, accent: '#0E7490' },
        { id: 'n', label: 'Novos', value: a.novos_leads || 0, accent: '#7c3aed' },
      ]} />
      <div style={{ display: 'grid', gridTemplateColumns: '1.2fr 0.8fr', gap: 12 }}>
        <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 16 }}>
          <div style={OH_LABEL}>Ocupação por faixa</div>
          {slots.map(s => {
            const cap = Number(s.capacidade) || 1;
            const n = Number(s.n_inscritos || s.ocupados || 0);
            return (
              <div key={s.id} style={{ marginTop: 10 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12 }}><span>{ohSlotTxt(s)}</span><span>{n}/{cap}</span></div>
                <div style={{ height: 6, background: '#e8edf5', borderRadius: 99, marginTop: 4 }}><div style={{ width: `${Math.min(100, (n / cap) * 100)}%`, height: '100%', background: '#3859D0', borderRadius: 99 }} /></div>
              </div>
            );
          })}
          {!slots.length && <div style={{ fontSize: 12, color: '#64748b', marginTop: 8 }}>Sem faixas horárias.</div>}
        </div>
        <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 16, fontSize: 13, color: '#112954', display: 'flex', flexDirection: 'column', gap: 8 }}>
          <div style={OH_LABEL}>Funil e pipeline</div>
          <div>Typeform / landing → {a.total_inscritos || 0}</div>
          <div>Qualificados Digi AI → {a.em_qualificacao || 0}</div>
          <div>Bilhetes → {a.confirmados || 0}</div>
          <div>Check-in → {a.presentes || 0}</div>
          <div>Clientes CRM {a.clientes_crm || 0} · Novos {a.novos_leads || 0}</div>
          <div>Pipeline {roi?.pipeline_k ?? '—'} k€ · ROI {roi?.roi_ratio == null ? '—' : Number(roi.roi_ratio).toFixed(2)}</div>
        </div>
      </div>
    </div>
  );
};

const OH_FOLLOWUP_PASSOS = [
  { id: 'fu_confirmacao', label: 'Confirmação', accent: '#112954' },
  { id: 'fu_d7', label: 'D-7', accent: '#3859D0' },
  { id: 'fu_d3', label: 'D-3', accent: '#3859D0' },
  { id: 'fu_d1', label: 'D-1', accent: '#3859D0' },
  { id: 'fu_obrigado', label: 'Presentes', accent: '#15803d' },
  { id: 'fu_noshow', label: 'No-show', accent: '#b45309' },
  { id: 'fu_handoff_comercial', label: 'Comercial', accent: '#7c3aed' },
];

const OHTabFollowup = ({ eventId }) => {
  const [data, setData] = React.useState(null);
  const [passo, setPasso] = React.useState(null);
  const [draft, setDraft] = React.useState(null);
  const [busy, setBusy] = React.useState('');
  const [err, setErr] = React.useState('');

  const load = React.useCallback(() => {
    ohApi(`/api/marketing/openhouses/${eventId}/followup`)
      .then(setData)
      .catch(e => setErr(e.message || 'Erro ao carregar follow-up'));
  }, [eventId]);
  React.useEffect(() => { load(); }, [load]);

  const peca = (data?.pecas || []).find(p => p.formato === passo) || null;
  const contagem = (data?.passos || []).find(p => p.formato === passo) || { enviados: 0, pendentes: 0 };
  React.useEffect(() => {
    if (!peca) { setDraft(null); return; }
    setDraft({
      assunto: peca.assunto || '',
      body: peca.body || '',
      body_html: peca.body_html || '',
      cta: peca.cta || '',
    });
  }, [peca?.id, peca?.updated_at]);

  async function gerar() {
    if (!passo) return;
    setBusy('gerar'); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/pecas/gerar`, {
        method: 'POST', body: JSON.stringify({ formato: passo }),
      });
      await load();
    } catch (e) { setErr(e.message || 'Erro ao gerar'); }
    finally { setBusy(''); }
  }
  async function guardar() {
    if (!peca || !draft) return;
    setBusy('save'); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/pecas/${peca.id}`, {
        method: 'PATCH', body: JSON.stringify(draft),
      });
      await load();
    } catch (e) { setErr(e.message || 'Erro ao guardar'); }
    finally { setBusy(''); }
  }
  async function enviar() {
    if (!passo) return;
    setBusy('send'); setErr('');
    try {
      await ohApi(`/api/marketing/openhouses/${eventId}/followup/${passo}/enviar`, {
        method: 'POST', body: '{}',
      });
      await load();
    } catch (e) { setErr(e.message || 'Erro ao enviar'); }
    finally { setBusy(''); }
  }

  const envios = (data?.envios || []).filter(e => !passo || e.formato === passo);
  const preview = (draft?.body_html || peca?.body_html || '').replaceAll('{{nome}}', 'Participante').replaceAll('{{empresa}}', 'Empresa');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
      <OHKpiBar
        active={passo}
        onPick={id => setPasso(passo === id ? null : id)}
        items={OH_FOLLOWUP_PASSOS.map(p => {
          const c = (data?.passos || []).find(x => x.formato === p.id) || { enviados: 0, pendentes: 0 };
          return { id: p.id, label: p.label, value: `${c.enviados}/${c.enviados + c.pendentes}`, accent: p.accent };
        })}
      />
      {err && <div style={{ color: '#dc2626', fontSize: 13 }}>{err}</div>}
      {passo && peca && draft ? (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 12 }}>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, padding: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <div style={OH_LABEL}>{peca.titulo || passo}</div>
            <div style={{ fontSize: 12, color: '#64748b' }}>{peca.subtitulo}</div>
            <input value={draft.assunto} onChange={e => setDraft({ ...draft, assunto: e.target.value })} placeholder="Assunto" style={{ border: '1px solid #e2e8f0', borderRadius: 6, padding: '8px 10px', fontSize: 13 }} />
            <textarea value={draft.body} onChange={e => setDraft({ ...draft, body: e.target.value })} rows={4} placeholder="Texto" style={{ border: '1px solid #e2e8f0', borderRadius: 6, padding: '8px 10px', fontSize: 13, fontFamily: 'inherit' }} />
            <textarea value={draft.body_html} onChange={e => setDraft({ ...draft, body_html: e.target.value })} rows={8} placeholder="HTML do email" style={{ border: '1px solid #e2e8f0', borderRadius: 6, padding: '8px 10px', fontSize: 12, fontFamily: 'ui-monospace, monospace' }} />
            <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
              <button className="btn btn-sm" type="button" disabled={!!busy} onClick={gerar}>{busy === 'gerar' ? 'A gerar…' : 'Gerar copy'}</button>
              <button className="btn btn-sm" type="button" disabled={!!busy} onClick={guardar}>{busy === 'save' ? 'A guardar…' : 'Guardar'}</button>
              <button className="btn btn-sm" type="button" disabled={!!busy || !contagem.pendentes} onClick={enviar}>
                {busy === 'send' ? 'A enviar…' : `Enviar aos pendentes (${contagem.pendentes || 0})`}
              </button>
            </div>
          </div>
          <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden', minHeight: 360 }}>
            <div style={{ padding: '8px 12px', fontSize: 11, color: '#64748b', borderBottom: '1px solid #e2e8f0' }}>{draft.assunto || 'Pré-visualização'}</div>
            <iframe title="preview" sandbox="" srcDoc={preview || '<p style="font-family:sans-serif;color:#64748b">Sem copy. Gera o email.</p>'} style={{ width: '100%', height: 420, border: 0 }} />
          </div>
        </div>
      ) : (
        <div style={{ background: '#fff', border: '1px solid var(--border)', borderRadius: 10, overflow: 'auto' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
            <thead>
              <tr style={{ textAlign: 'left', color: '#94a3b8', fontSize: 11 }}>
                <th style={{ padding: '10px 12px' }}>Participante</th>
                <th style={{ padding: '10px 12px' }}>Empresa</th>
                <th style={{ padding: '10px 12px' }}>Passo</th>
                <th style={{ padding: '10px 12px' }}>Estado</th>
                <th style={{ padding: '10px 12px' }}>Enviado</th>
              </tr>
            </thead>
            <tbody>
              {envios.map(e => (
                <tr key={e.id} style={{ borderTop: '1px solid #f1f5f9' }}>
                  <td style={{ padding: '8px 12px', color: '#112954' }}>{e.nome || e.email || 'Comercial'}</td>
                  <td style={{ padding: '8px 12px', color: '#64748b' }}>{e.empresa || '—'}</td>
                  <td style={{ padding: '8px 12px' }}>{(OH_FOLLOWUP_PASSOS.find(p => p.id === e.formato) || {}).label || e.formato}</td>
                  <td style={{ padding: '8px 12px' }}>{e.estado}</td>
                  <td style={{ padding: '8px 12px', color: '#64748b' }}>{e.enviado_em ? ohFmtDate(e.enviado_em) : '—'}</td>
                </tr>
              ))}
              {!envios.length && (
                <tr><td colSpan={5} style={{ padding: 16, color: '#94a3b8' }}>Ainda não há envios. Escolhe um passo para editar e enviar.</td></tr>
              )}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
};

const OH_DETAIL_TABS = [
  { id: 'ficha', label: 'Ficha' },
  { id: 'conteudos', label: 'Produção de Conteúdos' },
  { id: 'inscricoes', label: 'Inscrições & CRM' },
  { id: 'followup', label: 'Follow-up' },
  { id: 'checkin', label: 'Check-in' },
  { id: 'dashboard', label: 'Dashboard' },
];

const OHEventDetail = ({ eventId, userName, userEmail, onBack, onDeleted, tab, setTab }) => {
  const [evento, setEvento] = React.useState(null);
  const [regs, setRegs] = React.useState([]);
  const [error, setError] = React.useState('');

  const fetchEvento = React.useCallback(() => {
    ohApi(`/api/marketing/openhouses/${eventId}`)
      .then(setEvento)
      .catch(e => setError(e.message || 'Erro ao carregar'));
  }, [eventId]);
  const fetchRegs = React.useCallback(() => {
    ohApi(`/api/marketing/openhouses/${eventId}/registrations`)
      .then(d => setRegs(Array.isArray(d) ? d : []))
      .catch(() => {});
  }, [eventId]);
  React.useEffect(() => { fetchEvento(); fetchRegs(); }, [fetchEvento, fetchRegs]);

  if (error && !evento) {
    return (
      <div>
        <OHTrilho segmentos={[{ label: 'Open Houses', onClick: onBack }, { label: 'Erro' }]} />
        <div style={{ color: 'var(--danger, #dc2626)', marginTop: 12 }}>{error}</div>
      </div>
    );
  }
  if (!evento) {
    return (
      <div>
        <OHTrilho segmentos={[{ label: 'Open Houses', onClick: onBack }, { label: 'A carregar…' }]} />
        <div style={{ color: 'var(--text-muted)', fontSize: 13, marginTop: 8 }}>A carregar Open House…</div>
      </div>
    );
  }

  const marcas = Array.isArray(evento.marcas) ? evento.marcas : [];
  const brandColor = ohBrandColor(marcas[0]);
  const MenuAccoes = window.ResMenuAccoes;
  const menuItens = [
    ...OH_DETAIL_TABS.map(t => ({
      id: t.id,
      label: t.label,
      checked: tab === t.id,
      onClick: () => setTab(t.id),
    })),
    { separador: true },
    { id: 'reload', label: 'Actualizar', sub: 'Recarregar ficha e inscrições', onClick: () => { fetchEvento(); fetchRegs(); } },
    { id: 'voltar', label: 'Voltar à lista', onClick: onBack },
  ];
  const menu = MenuAccoes
    ? <MenuAccoes icone items={menuItens} />
    : <button type="button" className="btn btn-sm" onClick={onBack}>← Lista</button>;

  const datas = `${ohFmtDate(evento.data_evento)}${evento.data_fim && ohIso(evento.data_fim) !== ohIso(evento.data_evento) ? ` – ${ohFmtDate(evento.data_fim)}` : ''}`;

  return (
    <div data-theme="light" style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#f8fafc' }}>
      <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: '16px 40px 0', flexShrink: 0 }}>
        <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
          MARKETING · OPEN HOUSES
        </div>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '-0.01em' }}>
              {evento.nome}
            </h1>
            <div style={{ fontSize: 12, color: '#64748b', marginTop: 6, display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
              {marcas[0] && <span style={{ color: brandColor, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em', fontSize: 10, fontFamily: 'var(--font-mono, monospace)' }}>{(OH_MARCA_LABEL[marcas[0]] || marcas[0]).toUpperCase()}</span>}
              <span>· {(OH_STATUS_CFG[evento.status] || {}).label || evento.status}</span>
              <span>· {evento.showroom_obj?.nome || OH_SHOWROOM_LABEL[evento.showroom] || evento.showroom}</span>
              <span>· {datas}</span>
              <span>· objectivo {evento.capacidade_max || 0} · {evento.n_inscritos || 0} inscritos</span>
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            {menu}
            <button type="button" onClick={onBack} title="Fechar" style={{
              background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
              width: 30, height: 30, borderRadius: 6, color: '#64748b',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 16,
            }}>×</button>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 0, marginTop: 14, marginLeft: -4, overflowX: 'auto' }}>
          {OH_DETAIL_TABS.map(t => (
            <button key={t.id} type="button" onClick={() => setTab(t.id)} style={{
              background: 'none', border: 0, outline: 'none',
              borderBottom: `2px solid ${t.id === tab ? brandColor : 'transparent'}`,
              color: t.id === tab ? '#283252' : '#94A4C4',
              fontSize: 13, fontWeight: t.id === tab ? 600 : 500,
              fontFamily: 'var(--font-display, Montserrat, sans-serif)', letterSpacing: '.01em',
              padding: '10px 16px 12px', cursor: 'pointer', whiteSpace: 'nowrap',
            }}>{t.label}</button>
          ))}
        </div>
      </div>
      <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '20px 40px 48px' }}>
      {tab === 'ficha' && (
        <OHEventForm
          key={evento.updated_at || evento.id}
          embedded
          event={evento}
          userName={userName}
          userEmail={userEmail}
          onSaved={saved => setEvento(saved)}
          onDeleted={onDeleted}
        />
      )}
      {tab === 'conteudos' && <OHTabConteudos eventId={eventId} evento={evento} onEvento={setEvento} />}
      {tab === 'inscricoes' && <OHTabInscricoes eventId={eventId} evento={evento} regs={regs} onRefresh={() => { fetchRegs(); fetchEvento(); }} />}
      {tab === 'followup' && <OHTabFollowup eventId={eventId} />}
      {tab === 'checkin' && <OHTabCheckin eventId={eventId} onRefresh={fetchRegs} />}
      {tab === 'dashboard' && <OHTabDashboard eventId={eventId} evento={evento} />}
      </div>
    </div>
  );
};


const MktOpenHousesScreen = ({ userName, userEmail }) => {
  const [selectedId, setSelectedId] = React.useState(() => {
    try {
      const id = localStorage.getItem('mkt-open-oh-id');
      if (id) { localStorage.removeItem('mkt-open-oh-id'); return id; }
    } catch {}
    return null;
  });
  const [showCreate, setShowCreate] = React.useState(() => {
    try { return !!(window.__mktInicioNovaOh || localStorage.getItem('mkt-inicio-nova-oh')); } catch {}
    return false;
  });
  const [filterMarca, setFilterMarca] = React.useState('all');
  const [detailTab, setDetailTab] = React.useState('ficha');
  const [eventos, setEventos] = React.useState(null);
  const [error, setError] = React.useState('');

  const load = React.useCallback(() => {
    setError('');
    ohApi('/api/marketing/openhouses')
      .then(d => setEventos(Array.isArray(d) ? d : []))
      .catch(e => { setError(e.message || 'Erro ao carregar'); setEventos([]); });
  }, []);

  React.useEffect(() => { load(); }, [load]);
  React.useEffect(() => {
    if (!(window.__mktInicioNovaOh || (typeof localStorage !== 'undefined' && localStorage.getItem('mkt-inicio-nova-oh')))) return;
    setShowCreate(true);
    setSelectedId(null);
    const t = setTimeout(() => {
      window.__mktInicioNovaOh = false;
      try { localStorage.removeItem('mkt-inicio-nova-oh'); } catch {}
    }, 600);
    return () => clearTimeout(t);
  }, []);

  React.useEffect(() => {
    if (selectedId) setDetailTab('ficha');
  }, [selectedId]);

  const voltarLista = () => { setSelectedId(null); setShowCreate(false); load(); };
  const emCurso = (eventos || []).filter(ev => ev.status === 'planeamento' || ev.status === 'confirmado').length;

  if (selectedId && !showCreate) {
    return (
      <OHEventDetail
        eventId={selectedId}
        userName={userName}
        userEmail={userEmail}
        tab={detailTab}
        setTab={setDetailTab}
        onBack={voltarLista}
        onDeleted={voltarLista}
      />
    );
  }

  if (showCreate) {
    return (
      <div data-theme="light" style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#f8fafc' }}>
        <div style={{ background: '#ffffff', borderBottom: '1px solid #ECEFF5', padding: '16px 40px 16px', flexShrink: 0 }}>
          <div style={{ fontSize: 11, fontWeight: 600, letterSpacing: '0.07em', color: '#94A4C4', textTransform: 'uppercase', marginBottom: 6, fontFamily: 'var(--font-mono, monospace)' }}>
            MARKETING · OPEN HOUSES
          </div>
          <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
            <h1 style={{ margin: 0, fontSize: 21, fontWeight: 700, color: '#283252', fontFamily: 'var(--font-display, Montserrat, sans-serif)' }}>Nova Open House</h1>
            <button type="button" onClick={() => setShowCreate(false)} title="Fechar" style={{
              background: 'none', border: '1px solid #e2e8f0', cursor: 'pointer',
              width: 30, height: 30, borderRadius: 6, color: '#64748b', fontSize: 16,
            }}>×</button>
          </div>
        </div>
        <div className="scrollbar" style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: '20px 40px 48px' }}>
          <OHEventForm
            userName={userName}
            userEmail={userEmail}
            onClose={() => setShowCreate(false)}
            onSaved={saved => { setShowCreate(false); if (saved?.id) setSelectedId(saved.id); else load(); }}
            onDeleted={() => setShowCreate(false)}
          />
        </div>
      </div>
    );
  }

  return (
    <div data-theme="light" style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, background: '#f8fafc' }}>
      <OHChromeHeader
        filterMarca={filterMarca}
        onMarca={setFilterMarca}
        meta={eventos ? `${emCurso} em curso · ${(eventos || []).length} no total` : 'A carregar…'}
      />
      <div className="scrollbar" style={{ overflowY: 'auto', flex: 1, minHeight: 0, padding: '28px 40px 64px', background: '#f8fafc' }}>
        <OHEventList
          onSelect={setSelectedId}
          onCreate={() => setShowCreate(true)}
          userName={userName}
          userEmail={userEmail}
          filterMarca={filterMarca}
          eventos={eventos}
          error={error}
          onReload={load}
        />
      </div>
    </div>
  );
};

window.MktOpenHousesScreen = MktOpenHousesScreen;
