const SEO_LIMITS = { TITLE: [30, 65], META: [70, 160] };
const seoField = (type) => (String(type).startsWith('TITLE') ? 'TITLE' : String(type).startsWith('META') ? 'META' : null);

const SeoLen = ({ text, field }) => {
  const n = String(text || '').length;
  const [min, max] = SEO_LIMITS[field] || [0, 9999];
  const ok = n >= min && n <= max;
  return <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, marginLeft: 6, color: ok ? window.SEO_C.ok : window.SEO_C.warn }}>{n} car.{field ? ` (${min}–${max})` : ''}</span>;
};

const SeoIssueMeta = ({ i }) => (
  <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>
    {window.SEO_MODE_LABELS[i.mode] || i.mode}{i.cms ? ' · ' + (window.SEO_CMS_LABELS[i.cms] || i.cms) : ''}
  </span>
);

// ── Sites ────────────────────────────────────────────────────────────────────
const SeoViewSites = ({ initialSiteId, marca }) => {
  const W = window;
  // /overview traz o score real por site (o /sites não conta issues).
  const { data, err, load } = W.useSeoLoad('/api/marketing/seo/overview', [marca]);
  const [focus, setFocus] = React.useState(initialSiteId);
  const [detail, setDetail] = React.useState(null);
  const [files, setFiles] = React.useState(null);
  const [gen, setGen] = React.useState(false);
  const [toast, setToast] = React.useState(null);
  const sites = data?.sites || [];

  React.useEffect(() => { if (!focus && sites[0]) setFocus(sites[0].id); }, [data]);
  const loadDetail = () => window.seoFetch('/api/marketing/seo/sites/' + focus).then(setDetail).catch(e => setToast({ text: e.message, tone: 'bad' }));
  React.useEffect(() => { if (!focus) return; setDetail(null); setFiles(null); loadDetail(); }, [focus]);

  const run = () => {
    window.seoFetch('/api/marketing/seo/sites/' + focus + '/run', { method: 'POST', body: '{}' })
      .then(() => setToast({ text: 'Análise iniciada. Os resultados aparecem quando o crawl terminar.' }))
      .catch(e => setToast({ text: 'Não foi iniciada: ' + e.message, tone: 'bad' }));
  };
  const generate = () => {
    setGen(true);
    window.seoFetch('/api/marketing/seo/sites/' + focus + '/generate-proposals', { method: 'POST', body: JSON.stringify({ limit: 10 }) })
      .then(r => { setToast({ text: `Propostas geradas: ${r.generated} de ${r.tried}. Revê-as em “Para aprovar”.` }); return loadDetail(); })
      .catch(e => setToast({ text: 'Não foram geradas: ' + e.message, tone: 'bad' }))
      .finally(() => setGen(false));
  };
  const showLlms = () => window.seoFetch('/api/marketing/seo/sites/' + focus + '/llms').then(setFiles).catch(e => setToast({ text: e.message, tone: 'bad' }));

  if (err) return <W.SeoError message={err} onRetry={load} />;
  if (!data) return <W.SeoLoading label="A carregar sites…" />;
  if (!sites.length) return <W.SeoCard><W.SeoEmpty title="Ainda não há sites.">Os sites vêm das marcas com website activo.</W.SeoEmpty></W.SeoCard>;
  const site = sites.find(s => s.id === focus) || detail?.site;
  const issues = detail?.issues || [];

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '240px minmax(0, 1fr)', gap: 14, alignItems: 'start' }}>
      <W.SeoCard pad={false}>
        <div style={{ padding: '8px 12px', borderBottom: '1px solid var(--border)' }}><W.SeoEyebrow>Sites</W.SeoEyebrow></div>
        {sites.map(s => {
          const on = focus === s.id;
          return (
            <button key={s.id} type="button" aria-pressed={on} onClick={() => setFocus(s.id)} style={{
              display: 'block', width: '100%', textAlign: 'left', padding: '9px 12px', border: 0, borderBottom: '1px solid var(--border)', cursor: 'pointer',
              font: 'inherit', color: 'var(--text)', background: on ? 'var(--bg-hover)' : 'transparent', boxShadow: on ? `inset 3px 0 0 ${W.SEO_C.accent}` : 'none',
            }}>
              <span style={{ display: 'block', fontSize: 13, fontWeight: 650 }}>{s.domain}</span>
              <span style={{ display: 'block', fontSize: 11, color: 'var(--text-dim)' }}>{W.SEO_CMS_LABELS[s.cms] || s.cms} · score {s.score}{s.critical ? ` · ${s.critical} críticos` : ''}</span>
            </button>
          );
        })}
      </W.SeoCard>
      <div style={{ minWidth: 0, display: 'grid', gap: 12 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 12, flexWrap: 'wrap' }}>
          <div>
            <W.SeoEyebrow>{W.SEO_CMS_LABELS[site?.cms] || site?.cms || 'Site'}</W.SeoEyebrow>
            <div style={{ fontSize: 17, fontWeight: 650, marginTop: 2 }}>{site?.domain || '…'}</div>
            <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 2 }}>
              Score {site?.score ?? '—'} · {W.seoN(site?.open)} problemas abertos · último crawl {W.seoHora(site?.last_crawl_at)}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
            {site?.cms && site.cms !== 'wordpress' ? (
              <>
                <a className="btn btn-sm" href={'/api/marketing/seo/sites/' + focus + '/export/llms.txt'} download style={{ textDecoration: 'none', color: 'inherit' }}>Descarregar llms.txt</a>
                <a className="btn btn-sm" href={'/api/marketing/seo/sites/' + focus + '/export/jsonld.json'} download style={{ textDecoration: 'none', color: 'inherit' }}>Descarregar JSON-LD</a>
              </>
            ) : <W.SeoButton onClick={showLlms}>Ver llms.txt</W.SeoButton>}
            <W.SeoButton disabled={gen} onClick={generate}>{gen ? 'A gerar…' : 'Gerar propostas (até 10)'}</W.SeoButton>
            <W.SeoButton kind="primary" onClick={run}>Analisar agora</W.SeoButton>
          </div>
        </div>
        {site?.cms && site.cms !== 'wordpress' && (
          <div className="card" style={{ fontSize: 12, color: 'var(--text-muted)', padding: '10px 12px' }}>
            {W.SEO_CMS_LABELS[site.cms] || site.cms} não tem escrita automática. Descarrega o llms.txt e o JSON-LD para o programador publicar; as correcções ficam na fila e marcam-se como feitas à mão. Nada é publicado pelo portal.
          </div>
        )}
        {files && (
          <W.SeoCard>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
              <W.SeoEyebrow>llms.txt proposto</W.SeoEyebrow>
              <W.SeoButton onClick={() => setFiles(null)}>Fechar</W.SeoButton>
            </div>
            <pre style={{ margin: 0, fontSize: 11, overflow: 'auto', maxHeight: 200, fontFamily: 'var(--font-mono)' }}>{files.llms}</pre>
          </W.SeoCard>
        )}
        {!detail ? <W.SeoLoading label="A carregar problemas…" /> : issues.length === 0 ? (
          <W.SeoCard><W.SeoEmpty title="Sem problemas abertos neste site.">O agente volta a analisar o site esta noite.</W.SeoEmpty></W.SeoCard>
        ) : (
          <W.SeoCard pad={false}>
            {issues.slice(0, 40).map(i => (
              <div key={i.id} style={{ padding: '12px 14px', borderBottom: '1px solid var(--border)' }}>
                <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                  <W.SeoBadge severity={i.severity} />
                  <W.SeoTypeBadge type={i.type} />
                  <span style={{ marginLeft: 'auto' }}><SeoIssueMeta i={i} /></span>
                </div>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, marginTop: 6, color: 'var(--text-dim)' }}>{i.url}</div>
                <div style={{ fontSize: 12, marginTop: 6, color: 'var(--text-muted)' }}>{i.before_text || '—'} → {i.after_text || '—'}</div>
              </div>
            ))}
            {issues.length > 40 && <div style={{ padding: '10px 14px', fontSize: 12, color: 'var(--text-muted)' }}>A mostrar 40 de {issues.length}. Vê o resto em “Para aprovar”.</div>}
          </W.SeoCard>
        )}
      </div>
      <W.SeoToast msg={toast} onDone={() => setToast(null)} />
    </div>
  );
};

// ── Para aprovar ─────────────────────────────────────────────────────────────
const SeoViewQueue = ({ marca }) => {
  const W = window;
  const [status, setStatus] = React.useState('open');
  const { data, err, load } = W.useSeoLoad('/api/marketing/seo/issues?status=' + encodeURIComponent(status), [marca]);
  const [toast, setToast] = React.useState(null);
  const [busy, setBusy] = React.useState(null);
  const labels = { ...W.SEO_STATUS_LABELS, ...(data?.statuses ? Object.fromEntries(Object.keys(data.statuses).map(k => [k, W.SEO_STATUS_LABELS[k] || data.statuses[k]])) : {}) };
  const OK_MSG = { approve: 'Aplicado no site.', reject: 'Rejeitado.', generate: 'Proposta gerada.', 'mark-done': 'Marcado como feito.', reopen: 'Reaberto.' };
  const act = (id, action) => {
    setBusy(id + action);
    window.seoFetch('/api/marketing/seo/issues/' + id + '/' + action, { method: 'POST', body: '{}' })
      .then(() => { setToast({ text: OK_MSG[action] || 'Feito.' }); return load(); })
      .catch(e => setToast({ text: 'Não foi feito: ' + e.message, tone: 'bad' }))
      .finally(() => setBusy(null));
  };

  const filter = (
    <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
      <W.SeoSeg ariaLabel="Estado" value={status} onChange={setStatus} options={Object.entries(labels).map(([id, label]) => ({ id, label }))} />
      {data && <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{W.seoN((data.issues || []).length)} {status === 'open' ? 'abertos' : 'neste estado'}</span>}
    </div>
  );
  if (err) return <div>{filter}<W.SeoError message={err} onRetry={load} /></div>;
  if (!data) return <div>{filter}<W.SeoLoading label="A carregar fila…" /></div>;
  const issues = data.issues || [];
  const queue = status === 'open' ? issues.filter(i => i.mode !== 'full-auto' || i.cms !== 'wordpress') : issues;
  if (!queue.length) {
    return <div>{filter}<W.SeoCard><W.SeoEmpty title={status === 'open' ? 'Nada à espera de aprovação.' : `Sem problemas em “${labels[status] || status}”.`}>
      {status === 'open' ? 'Quando o agente encontrar problemas novos, aparecem aqui.' : null}
    </W.SeoEmpty></W.SeoCard></div>;
  }
  return (
    <div>
      {filter}
      <W.SeoCard pad={false}>
        {queue.map(i => {
          const field = seoField(i.type);
          const generated = !!i.generated_by;
          const canWrite = generated && field && i.cms === 'wordpress';
          const b = (a) => busy === i.id + a;
          return (
            <div key={i.id} style={{ padding: '14px 16px', borderBottom: '1px solid var(--border)' }}>
              <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
                <W.SeoBadge severity={i.severity} />
                <W.SeoTypeBadge type={i.type} />
                <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{i.domain}</span>
                <span style={{ marginLeft: 'auto', display: 'inline-flex', gap: 10, alignItems: 'center' }}>
                  <SeoIssueMeta i={i} />
                  {i.status !== 'open' && <W.SeoStatusBadge status={i.status} />}
                </span>
              </div>
              <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, marginTop: 6, color: 'var(--text-dim)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={i.url}>{i.url}</div>
              <div style={{ fontSize: 12, margin: '6px 0 10px', color: 'var(--text-muted)' }}>{i.why}</div>
              <div style={{ display: 'grid', gridTemplateColumns: '90px minmax(0, 1fr)', gap: '4px 10px', fontSize: 12 }}>
                <span style={{ color: 'var(--text-dim)' }}>Actual</span>
                <span>{i.before_text || '—'}{field && <SeoLen text={/^\(sem /.test(i.before_text || '') ? '' : i.before_text} field={field} />}</span>
                <span style={{ color: 'var(--text-dim)' }}>Proposta</span>
                <span>
                  <span style={{ color: generated ? 'var(--text)' : 'var(--text-muted)' }}>{i.after_text || '—'}</span>
                  {field && generated && <SeoLen text={i.after_text} field={field} />}
                  {generated
                    ? <span style={{ fontSize: 10.5, marginLeft: 6, color: 'var(--text-dim)' }}>gerada por {i.generated_by}</span>
                    : (i.status === 'open' && <span style={{ fontSize: 11, color: 'var(--text-dim)' }}> · indicação genérica, não é escrita no site</span>)}
                </span>
              </div>
              {i.status === 'open' && (
                <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 12 }}>
                  {canWrite && <W.SeoButton kind="primary" disabled={!!busy} onClick={() => act(i.id, 'approve')}>{b('approve') ? 'A escrever…' : 'Aprovar e escrever'}</W.SeoButton>}
                  {field && <W.SeoButton disabled={!!busy} onClick={() => act(i.id, 'generate')}>{b('generate') ? 'A gerar…' : (generated ? 'Gerar de novo' : 'Gerar proposta')}</W.SeoButton>}
                  <W.SeoButton disabled={!!busy} onClick={() => act(i.id, 'mark-done')}>{b('mark-done') ? 'A marcar…' : 'Marcar como feito'}</W.SeoButton>
                  <W.SeoButton kind="danger" disabled={!!busy} onClick={() => act(i.id, 'reject')} style={{ marginLeft: 'auto' }}>Rejeitar</W.SeoButton>
                </div>
              )}
              {(i.status === 'done_manual' || i.status === 'rejected') && (
                <div style={{ marginTop: 12 }}><W.SeoButton disabled={!!busy} onClick={() => act(i.id, 'reopen')}>{b('reopen') ? 'A reabrir…' : 'Reabrir'}</W.SeoButton></div>
              )}
            </div>
          );
        })}
      </W.SeoCard>
      <W.SeoToast msg={toast} onDone={() => setToast(null)} />
    </div>
  );
};

// ── Histórico ────────────────────────────────────────────────────────────────
const SeoViewHistory = () => {
  const W = window;
  const { data, err, load } = W.useSeoLoad('/api/marketing/seo/history', []);
  const [toast, setToast] = React.useState(null);
  if (err) return <W.SeoError message={err} onRetry={load} />;
  if (!data) return <W.SeoLoading label="A carregar histórico…" />;
  const rows = data.history || [];
  if (!rows.length) return <W.SeoCard><W.SeoEmpty title="Ainda não há alterações.">Aqui ficam as propostas geradas, aprovações, reversões e o que for marcado como feito, com quem e quando.</W.SeoEmpty></W.SeoCard>;
  const revert = (r) => window.seoFetch('/api/marketing/seo/issues/' + r.issue_id + '/revert', { method: 'POST', body: '{}' })
    .then(() => { setToast({ text: 'Alteração revertida.' }); load(); })
    .catch(e => setToast({ text: 'Não foi revertida: ' + e.message, tone: 'bad' }));
  const cols = '120px minmax(140px, 0.8fr) minmax(220px, 2fr) auto';
  return (
    <W.SeoCard pad={false} style={{ overflowX: 'auto' }}>
      <div style={{ display: 'grid', gridTemplateColumns: cols, gap: 12, padding: '8px 14px', minWidth: 720, borderBottom: '1px solid var(--border)' }}>
        {['Quando', 'Acção', 'Detalhe', ''].map((h, k) => <W.SeoTh key={k}>{h}</W.SeoTh>)}
      </div>
      {rows.map(r => (
        <div key={r.id} style={{ display: 'grid', gridTemplateColumns: cols, gap: 12, padding: '10px 14px', minWidth: 720, borderBottom: '1px solid var(--border)', fontSize: 12, alignItems: 'center' }}>
          <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--text-muted)' }} title={new Date(r.created_at).toLocaleString('pt-PT')}>{W.seoHora(r.created_at)}</span>
          <span>
            <span style={{ display: 'block', fontWeight: 650 }}>{W.SEO_ACTION_LABELS[r.action] || r.action}</span>
            <span style={{ display: 'block', fontSize: 11, color: 'var(--text-dim)' }}>{r.actor || '—'}</span>
          </span>
          <span style={{ minWidth: 0 }}>
            <span style={{ display: 'block' }}>{r.domain} · {W.seoTypeLabel(r.field)}</span>
            <span style={{ display: 'block', color: 'var(--text-muted)' }}>{r.before_text || '—'} → {r.after_text || '—'}</span>
          </span>
          <span>{r.revertable && r.issue_id ? <W.SeoButton onClick={() => revert(r)}>Reverter</W.SeoButton> : null}</span>
        </div>
      ))}
      <W.SeoToast msg={toast} onDone={() => setToast(null)} />
    </W.SeoCard>
  );
};

// ── Configuração ─────────────────────────────────────────────────────────────
// Estado das ligações por site (só presença/propriedade, nunca segredos).
const SEO_CONN_COLS = [
  { k: 'website', label: 'WordPress' }, { k: 'gsc', label: 'Search Console' }, { k: 'ga4', label: 'GA4' },
  { k: 'bing', label: 'Bing' }, { k: 'pagespeed', label: 'PageSpeed' }, { k: 'dataforseo', label: 'DataForSEO' },
];
function seoConnCell(row, k) {
  const c = row[k];
  if (k === 'website') {
    if (!c) return { tone: '', text: 'n/a', title: 'Sem escrita automática (não é WordPress).' };
    return c.configured ? { tone: 'badge-success', text: 'Ligado' } : { tone: 'badge-warning', text: 'Sem credenciais', title: 'Ligações → Website / Blog da marca.' };
  }
  if (!c?.configured) return { tone: 'badge-warning', text: 'Por ligar', title: 'Configura em Marketing · Ligações.' };
  if (k === 'ga4' && !c.property) return { tone: 'badge-warning', text: 'Sem property', title: 'Falta o Property ID deste site no GA4.' };
  const job = (row.jobs || []).find(j => j.kind === 'measure');
  const err = job?.error && String(job.error).split(' · ').find(p => p.startsWith(k === 'gsc' ? 'gsc:' : k + ':'));
  if (err) return { tone: 'badge-danger', text: 'Erro', title: err };
  if (c.data) return { tone: 'badge-success', text: c.data.last ? 'Dados ' + c.data.last.slice(5).split('-').reverse().join('/') : 'Com dados' };
  return { tone: 'badge-info', text: 'Ligado', title: 'Ainda sem dados; aparecem após a próxima medição.' };
}
const SeoConnStatus = () => {
  const W = window;
  const { data, err, load } = W.useSeoLoad('/api/marketing/seo/status', []);
  if (err) return <W.SeoError message={err} onRetry={load} />;
  if (!data) return <W.SeoLoading label="A verificar ligações…" />;
  const cols = 'minmax(170px, 1.3fr) repeat(6, minmax(92px, 1fr))';
  return (
    <div style={{ display: 'grid', gap: 10 }}>
      <W.SeoSection right={<W.SeoButton onClick={W.seoGoLigacoes}>Abrir Ligações</W.SeoButton>}>Ligações por site</W.SeoSection>
      <W.SeoCard pad={false} style={{ overflowX: 'auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: cols, gap: 10, padding: '8px 14px', minWidth: 820, borderBottom: '1px solid var(--border)' }}>
          {['Site', ...SEO_CONN_COLS.map(c => c.label)].map(h => <W.SeoTh key={h}>{h}</W.SeoTh>)}
        </div>
        {(data.sites || []).map(row => (
          <div key={row.site_id} style={{ display: 'grid', gridTemplateColumns: cols, gap: 10, padding: '9px 14px', minWidth: 820, borderBottom: '1px solid var(--border)', alignItems: 'center' }}>
            <span style={{ minWidth: 0 }}>
              <span style={{ display: 'block', fontSize: 13, fontWeight: 600 }}>{row.domain}</span>
              <span style={{ display: 'block', fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={(row.gsc?.candidates || []).join(' · ')}>{(row.gsc?.candidates || [])[0] || '—'}</span>
            </span>
            {SEO_CONN_COLS.map(c => {
              const v = seoConnCell(row, c.k);
              return <span key={c.k}><span className={'badge ' + v.tone} title={v.title || ''}>{v.text}</span></span>;
            })}
          </div>
        ))}
      </W.SeoCard>
      {data.service_account ? (
        <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.5 }}>
          Conta de serviço: <span style={{ fontFamily: 'var(--font-mono)', color: 'var(--text)' }}>{data.service_account}</span>
          <button type="button" className="btn btn-xs" style={{ marginLeft: 8 }} onClick={() => navigator.clipboard && navigator.clipboard.writeText(data.service_account)}>Copiar</button>
          <div>Adiciona-a como utilizador “Restrito” em cada propriedade do Search Console e como “Leitor” em cada propriedade GA4.</div>
        </div>
      ) : null}
    </div>
  );
};

const SeoViewConfig = () => {
  const W = window;
  const [data, setData] = React.useState(null);
  const [sites, setSites] = React.useState([]);
  const [err, setErr] = React.useState('');
  const [saving, setSaving] = React.useState(false);
  const [dirty, setDirty] = React.useState(false);
  const [toast, setToast] = React.useState(null);
  W.useMktZoneLoading(data == null && !err);
  const load = () => {
    setErr('');
    return Promise.all([window.seoFetch('/api/marketing/seo/rules'), window.seoFetch('/api/marketing/seo/sites')])
      .then(([rules, s]) => { setData(rules); setSites(s.sites || []); setDirty(false); })
      .catch(e => setErr(e.message));
  };
  React.useEffect(() => { load(); }, []);
  if (err) return <W.SeoError message={err} onRetry={load} />;
  if (!data) return <W.SeoLoading label="A carregar configuração…" />;

  const types = Object.keys(data.types || {});
  const ruleFor = (siteId, type) => (data.rules || []).find(r => r.site_id === siteId && r.issue_type === type);
  const setMode = (siteId, type, mode) => {
    setDirty(true);
    setData(prev => {
      const rules = (prev.rules || []).filter(r => !(r.site_id === siteId && r.issue_type === type));
      rules.push({ site_id: siteId, issue_type: type, mode, approver: ruleFor(siteId, type)?.approver || '', notify: true });
      return { ...prev, rules };
    });
  };
  const save = () => {
    setSaving(true);
    window.seoFetch('/api/marketing/seo/rules', { method: 'PUT', body: JSON.stringify({ rules: data.rules }) })
      .then(() => { setDirty(false); setToast({ text: 'Configuração guardada.' }); })
      .catch(e => setToast({ text: 'Não foi guardada: ' + e.message, tone: 'bad' }))
      .finally(() => setSaving(false));
  };
  const sel = { height: 28, padding: '0 6px', borderRadius: 6, border: '1px solid var(--border)', background: 'var(--bg)', color: 'var(--text)', fontSize: 12 };

  return (
    <div style={{ display: 'grid', gap: 12 }}>
      <SeoConnStatus />
      <W.SeoSection style={{ marginTop: 10 }}>Modo de aprovação</W.SeoSection>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
        <div style={{ fontSize: 12, color: 'var(--text-muted)', maxWidth: 680, lineHeight: 1.5 }}>
          Modo por site e tipo de problema. <b>Automático</b> só escreve title e meta no WordPress, e apenas com texto gerado e validado — nunca indicações genéricas.
        </div>
        <W.SeoButton kind="primary" disabled={saving || !dirty} onClick={save}>{saving ? 'A guardar…' : (dirty ? 'Guardar' : 'Guardado')}</W.SeoButton>
      </div>
      {sites.map(s => (
        <W.SeoCard key={s.id}>
          <div style={{ display: 'flex', gap: 10, alignItems: 'baseline', marginBottom: 10, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 13, fontWeight: 650 }}>{s.domain}</span>
            <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>{W.SEO_CMS_LABELS[s.cms] || s.cms}</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(260px, 1fr))', gap: '6px 18px' }}>
            {types.map(type => (
              <label key={type} style={{ fontSize: 12, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
                <span style={{ color: 'var(--text-muted)' }}>{W.seoTypeLabel(type)}</span>
                <select aria-label={`${s.domain} · ${W.seoTypeLabel(type)}`} value={ruleFor(s.id, type)?.mode || 'semi-auto'} onChange={e => setMode(s.id, type, e.target.value)} style={sel}>
                  <option value="manual">Manual</option>
                  <option value="semi-auto">Semi-automático</option>
                  <option value="full-auto">Automático</option>
                </select>
              </label>
            ))}
          </div>
        </W.SeoCard>
      ))}
      <W.SeoToast msg={toast} onDone={() => setToast(null)} />
    </div>
  );
};

Object.assign(window, { SeoViewSites, SeoViewQueue, SeoViewHistory, SeoViewConfig });
