/* SEO · componentes e etiquetas partilhados (padrão dos módulos Actividade/Ligações).
   Tudo o que é usado em mais que um ficheiro JSX vive aqui e é exposto em window. */

// Cores: tokens do portal (tokens.css).
const SEO_C = { accent: 'var(--ai-500)', ok: 'var(--success)', warn: 'var(--warning)', bad: 'var(--danger)' };

const seoSeverityColor = { critical: SEO_C.bad, medium: SEO_C.warn, low: 'var(--text-muted)' };
const seoSeverityLabel = { critical: 'Crítico', medium: 'Médio', low: 'Baixo' };
const seoSeverityDot = { critical: '●', medium: '●', low: '●' };

const SEO_TYPE_LABELS = {
  TITLE_MISSING: 'Title em falta', TITLE_TOO_SHORT: 'Title curto demais', TITLE_DUPLICATE: 'Title duplicado',
  META_MISSING: 'Meta description em falta', META_TOO_LONG: 'Meta longa demais', META_DUPLICATE: 'Meta duplicada',
  H1_MISSING: 'H1 em falta', H1_MULTIPLE: 'Vários H1', ALT_MISSING: 'Imagens sem alt',
  CANONICAL_MISSING: 'Canonical em falta', HREFLANG_MISSING: 'Hreflang em falta', SCHEMA_MISSING: 'Schema em falta',
  LLMS_TXT_MISSING: 'llms.txt em falta', CWV_POOR: 'Core Web Vitals fracos', AI_NOT_CITED: 'Marca não citada em AI',
};
const SEO_MODE_LABELS = { manual: 'Manual', 'semi-auto': 'Semi-automático', 'full-auto': 'Automático' };
const SEO_CMS_LABELS = { wordpress: 'WordPress', nextjs: 'Next.js', outro: 'Outro CMS' };
const SEO_STATUS_LABELS = {
  open: 'Abertos', applied: 'Aplicados', resolved: 'Resolvidos', done_manual: 'Feitos à mão',
  rejected: 'Rejeitados', reverted: 'Revertidos',
};
const SEO_STATUS_ONE = {
  open: 'Aberto', applied: 'Aplicado', resolved: 'Resolvido', done_manual: 'Feito à mão',
  rejected: 'Rejeitado', reverted: 'Revertido',
};
const SEO_STATUS_TONE = { open: SEO_C.accent, applied: SEO_C.ok, resolved: SEO_C.ok, done_manual: SEO_C.ok, rejected: 'var(--text-muted)', reverted: SEO_C.warn };
const SEO_SUG_STATUS = { queued: 'Na fila', draft: 'Rascunho', sent: 'Enviado para Produção', rejected: 'Rejeitado' };
const SEO_TRIGGER_LABELS = { pos_4_20: 'Posição 4–20', sem_pagina: 'Sem página', canibalizacao: 'Canibalização', nao_citado: 'Não citada em AI' };
const SEO_ACTION_LABELS = {
  apply: 'Aplicado no site', approve: 'Aprovado', approve_failed: 'Aprovação sem escrita', revert: 'Revertido',
  generate: 'Proposta gerada', mark_done: 'Marcado como feito', reopen: 'Reaberto',
};
const SEO_SOURCES = {
  gsc: { label: 'Search Console', feeds: 'pesquisas, posições e sugestões de conteúdo' },
  ga4: { label: 'Google Analytics 4', feeds: 'visitas vindas de assistentes AI' },
  bing: { label: 'Bing Webmaster', feeds: 'pesquisas no Bing' },
  dataforseo: { label: 'DataForSEO', feeds: 'volume de pesquisa e menções em modelos AI' },
  pagespeed: { label: 'PageSpeed', feeds: 'Core Web Vitals' },
};

const seoTypeLabel = (t) => SEO_TYPE_LABELS[t] || t;
const seoN = (n) => Number(n || 0).toLocaleString('pt-PT');
function seoHora(iso) {
  const d = new Date(iso);
  if (!iso || Number.isNaN(d.getTime())) return '—';
  const time = d.toLocaleTimeString('pt-PT', { hour: '2-digit', minute: '2-digit' });
  if (d.toDateString() === new Date().toDateString()) return 'hoje ' + time;
  return d.toLocaleDateString('pt-PT', { day: '2-digit', month: '2-digit' }) + ' ' + time;
}

// Lê a última medição de cada site e diz que fontes faltam (do texto de seo_jobs.error).
function seoSourceStatus(jobs) {
  const last = (jobs || []).find(j => j.kind === 'measure');
  const out = {};
  for (const k of Object.keys(SEO_SOURCES)) out[k] = { ok: !!last, reason: last ? '' : 'Ainda sem medição.' };
  if (last?.error) {
    for (const part of String(last.error).split(' · ')) {
      const m = part.match(/^([a-z0-9]+):\s*(.*)$/i);
      if (m && out[m[1]]) out[m[1]] = { ok: false, reason: m[2] };
    }
  }
  return { at: last?.finished_at || last?.started_at || null, sources: out };
}

const seoGoLigacoes = () => { window.location.hash = 'screen=marketing&sub=ligacoes'; };

// Rótulos e cabeçalhos de tabela: mesmos estilos que Clipping (CLIP_LABEL / CLIP_TH).
const SeoEyebrow = ({ children, style }) => (
  <div style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.06em', fontFamily: 'var(--font-mono)', ...style }}>{children}</div>
);
const SeoSection = ({ children, right, style }) => (
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap', ...style }}>
    <div style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-dim)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{children}</div>
    {right ? <div style={{ display: 'flex', gap: 8 }}>{right}</div> : null}
  </div>
);
const SEO_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 SeoTh = ({ children, style }) => <div style={{ ...SEO_TH, padding: 0, ...style }}>{children}</div>;

// Badges com as classes do portal (.badge / .badge-*).
const SEO_SEV_BADGE = { critical: 'badge badge-danger', medium: 'badge badge-warning', low: 'badge' };
const SeoBadge = ({ severity }) => (
  <span className={SEO_SEV_BADGE[severity] || 'badge'}>{seoSeverityLabel[severity] || severity}</span>
);

const SEO_STATUS_BADGE = { open: 'badge badge-info', applied: 'badge badge-success', resolved: 'badge badge-success', done_manual: 'badge badge-success', rejected: 'badge', reverted: 'badge badge-warning' };
const SeoStatusBadge = ({ status, label }) => (
  <span className={SEO_STATUS_BADGE[status] || 'badge'}>{label || SEO_STATUS_ONE[status] || status}</span>
);

const SeoTypeBadge = ({ type }) => (
  <span title={type} style={{ fontSize: 13, fontWeight: 650 }}>{seoTypeLabel(type)}</span>
);

// KPI: o componente partilhado do portal (window.MktKpi), como Clipping e Plano.
const SeoStat = ({ label, value, hint, tone, trend }) => {
  const K = window.MktKpi;
  const v = tone ? <span style={{ color: tone }}>{value}</span> : value;
  if (K) return <K label={label} value={v} sub={hint} trend={trend} />;
  return (
    <div className="card" style={{ padding: 16 }}>
      <SeoEyebrow>{label}</SeoEyebrow>
      <div className="font-display" style={{ fontSize: 28, fontWeight: 600, lineHeight: 1, marginTop: 4 }}>{v}</div>
      {hint ? <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{hint}</div> : null}
      {trend}
    </div>
  );
};

// Cartão: classe .card do portal.
const SeoCard = ({ children, style, pad = true }) => (
  <div className="card" style={{ overflow: 'hidden', ...(pad ? { padding: 16 } : {}), ...style }}>{children}</div>
);

// Botões: classes do portal. primary = .btn-ai (acção principal), default = .btn .btn-sm, ghost = .btn-ghost.
const seoBtn = (kind = 'ghost') => (kind === 'danger' ? { color: 'var(--danger)' } : {});
const SEO_BTN_CLASS = { primary: 'btn btn-sm btn-ai', ghost: 'btn btn-sm btn-ghost', danger: 'btn btn-sm', default: 'btn btn-sm' };
const SeoButton = ({ kind, children, style, ...rest }) => (
  <button type="button" className={SEO_BTN_CLASS[kind] || SEO_BTN_CLASS.default} {...rest}
    style={{ whiteSpace: 'nowrap', ...seoBtn(kind), ...(rest.disabled ? { opacity: 0.5, cursor: 'default' } : {}), ...style }}>{children}</button>
);

// Filtro segmentado: o mesmo padrão dos botões de período em Clipping (7d/30d/90d).
const SeoSeg = ({ value, onChange, options, ariaLabel }) => (
  <div role="group" aria-label={ariaLabel} style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
    {options.map(o => {
      const on = value === o.id;
      return (
        <button key={o.id} type="button" aria-pressed={on} data-seg={o.id} onClick={() => onChange(o.id)} style={{
          padding: '4px 10px', borderRadius: 6, fontSize: 11, fontWeight: 600, cursor: 'pointer',
          border: '1px solid var(--border)',
          background: on ? 'var(--ai-500)' : 'var(--bg-elev)',
          color: on ? 'var(--bg-elev)' : 'var(--text-muted)',
        }}>{o.label}</button>
      );
    })}
  </div>
);

const SeoEmpty = ({ title, children, action }) => (
  <div style={{ padding: '36px 16px', textAlign: 'center' }}>
    <div style={{ fontSize: 13, color: 'var(--text)', fontWeight: 550 }}>{title}</div>
    {children ? <div style={{ fontSize: 12, color: 'var(--text-muted)', marginTop: 6, maxWidth: 560, marginInline: 'auto', lineHeight: 1.5 }}>{children}</div> : null}
    {action ? <div style={{ marginTop: 12 }}>{action}</div> : null}
  </div>
);

const SeoLoading = ({ label = 'A carregar…' }) => (
  <div role="status" style={{ fontSize: 13, color: 'var(--text-muted)', padding: '8px 0' }}>{label}</div>
);

const SeoError = ({ message, onRetry }) => (
  <div role="alert" className="card" style={{ padding: 16, display: 'flex', gap: 12, alignItems: 'center', justifyContent: 'space-between', borderColor: 'color-mix(in oklch, var(--danger) 35%, var(--border))' }}>
    <span style={{ fontSize: 13, color: 'var(--danger)' }}>{/sess[aã]o/i.test(message || '') ? 'A sessão expirou. Volta a entrar no portal.' : (message || 'Não foi possível carregar.')}</span>
    {onRetry ? <SeoButton onClick={onRetry}>Tentar de novo</SeoButton> : null}
  </div>
);

// Fontes em falta → estado vazio que explica qual ligação configurar.
const SeoMissingSources = ({ keys, jobs, title }) => {
  const st = seoSourceStatus(jobs);
  const missing = keys.filter(k => !st.sources[k]?.ok);
  return (
    <SeoCard>
      <SeoEmpty
        title={title}
        action={<SeoButton kind="primary" onClick={seoGoLigacoes}>Abrir Ligações</SeoButton>}>
        {missing.length
          ? <>Faltam dados de {missing.map(k => SEO_SOURCES[k].label).join(' e ')}. Configura {missing.length > 1 ? 'as ligações' : 'a ligação'} em Marketing · Ligações; a próxima medição nocturna preenche este separador.</>
          : <>As fontes estão ligadas mas ainda não devolveram dados. A próxima medição nocturna preenche este separador.</>}
      </SeoEmpty>
      <div style={{ borderTop: '1px solid var(--border)', padding: '10px 4px 0', display: 'grid', gap: 6 }}>
        {keys.map(k => (
          <div key={k} style={{ display: 'flex', gap: 8, fontSize: 12, alignItems: 'baseline' }}>
            <span style={{ width: 8, height: 8, borderRadius: 99, flexShrink: 0, background: st.sources[k]?.ok ? 'var(--success)' : 'var(--warning)' }} />
            <span style={{ fontWeight: 600, minWidth: 140 }}>{SEO_SOURCES[k].label}</span>
            <span style={{ color: 'var(--text-muted)' }}>{st.sources[k]?.ok ? 'Ligado' : (st.sources[k]?.reason || 'Sem ligação')} · {SEO_SOURCES[k].feeds}</span>
          </div>
        ))}
      </div>
    </SeoCard>
  );
};

const SeoToast = ({ msg, onDone }) => {
  React.useEffect(() => { if (!msg) return undefined; const t = setTimeout(onDone, 3500); return () => clearTimeout(t); }, [msg]);
  if (!msg) return null;
  const bad = msg.tone === 'bad';
  return (
    <div role="status" style={{
      position: 'fixed', right: 24, bottom: 24, zIndex: 60, maxWidth: 420, padding: '10px 14px', borderRadius: 10,
      background: 'var(--bg-elev)', border: `1px solid ${bad ? 'var(--danger)' : 'var(--border)'}`,
      boxShadow: '0 8px 24px rgba(15,23,42,.12)', fontSize: 13, color: bad ? SEO_C.bad : 'var(--text)',
    }}>{msg.text}</div>
  );
};

// Hook de carregamento comum: dados, erro, recarregar.
function useSeoLoad(url, deps) {
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState('');
  const load = React.useCallback(() => {
    setErr('');
    return window.seoFetch(url).then(setData).catch(e => setErr(e.message));
  }, [url]);
  React.useEffect(() => { setData(null); load(); }, [...(deps || []), url]);
  window.useMktZoneLoading(!data && !err);
  return { data, err, load, setData };
}

const Sparkline = ({ values, width = 120, height = 28, color = 'var(--ai-500)' }) => {
  if (!values || values.length < 2) return null;
  const max = Math.max(...values), min = Math.min(...values);
  const range = max - min || 1;
  const step = width / (values.length - 1);
  const pts = values.map((v, i) => `${(i * step).toFixed(1)},${(height - ((v - min) / range) * height).toFixed(1)}`).join(' ');
  return <svg width={width} height={height} style={{ display: 'block' }}><polyline fill="none" stroke={color} strokeWidth="1.5" points={pts} /></svg>;
};

Object.assign(window, {
  SEO_C, SEO_TYPE_LABELS, SEO_MODE_LABELS, SEO_CMS_LABELS, SEO_STATUS_LABELS, SEO_STATUS_ONE, SEO_SUG_STATUS,
  SEO_TRIGGER_LABELS, SEO_ACTION_LABELS, SEO_SOURCES,
  seoSeverityColor, seoSeverityLabel, seoSeverityDot, seoTypeLabel, seoN, seoHora, seoSourceStatus, seoGoLigacoes, seoBtn,
  SeoEyebrow, SeoSection, SEO_TH, SeoTh, SeoBadge, SeoStatusBadge, SeoTypeBadge, SeoStat, SeoCard, SeoButton, SeoSeg, SeoEmpty, SeoLoading,
  SeoError, SeoMissingSources, SeoToast, useSeoLoad, Sparkline,
});
