/* global React, Icon, Button, Badge, Card, Chip, KPI, FooterPager, SearchableSelect, PageHead, brl, calcPeriod, ymdLocal,
   lerTodas, buildCustoMaps, custoDoItemPedido, carregarCustoKits, useProdutosList, tmSemAcento */
// ─── MERCADO LIVRE, FASE 1a (08/10/2026) ──────────────────────────────────────────────────────────────────────
// A tela do ML, ISOLADA: conectar as lojas, ler anúncios e vendas, vincular anúncio ↔ produto e ver o resultado.
// Nada aqui mexe em estoque, pedido, recebível ou DRE (isso é a Fase 1b/2). Quem fala com o ML é a Edge Function
// `mercadolivre-proxy`; o token nunca chega aqui.
//   · Configurações › Mercado Livre (`MlConexoesConfig`, governada por config): Conectar / Desconectar.
//   · Marketplace › Mercado Livre: Lojas (`MlLojas`), Anúncios (`MlAnuncios`), Vendas e resultado (`MlVendas`).
// O CUSTO do resultado passa pela MESMA cascata do resto do app (custoDoItemPedido + custo do kit): o banco devolve
// os agregados por produto (ml_resumo) e a tela aplica o custo — duas fórmulas de custo divergiriam.

const ML_EDGE = 'https://ilgxpxfdrhshitkozlqs.supabase.co/functions/v1/mercadolivre-proxy';
const ML_DESDE = '2026-01-01';
const ML_TAXA_DICA = 'O ML cobra no faturamento; conferência em Lojas › Conferir mês.';

// 🔴 `window.tmFetchSeguro`, nunca `fetch` cru: a guarda de escrita mora nele. Devolve { ok, status, data, erro },
// nunca lança — quem chama decide o que mostrar.
async function mlChamar(caminho, opcoes) {
  const o = opcoes || {};
  const headers = { 'Content-Type': 'application/json', apikey: window.tmAnonKey || '' };
  try {
    if (window.tmSupabase) {
      const s = await window.tmSupabase.auth.getSession();
      const tok = s && s.data && s.data.session && s.data.session.access_token;
      if (tok) headers.Authorization = 'Bearer ' + tok;
    }
    const resp = await window.tmFetchSeguro(ML_EDGE + caminho, { method: o.method || 'GET', headers, body: o.body ? JSON.stringify(o.body) : undefined });
    const data = await resp.json().catch(() => null);
    if (!resp.ok) return { ok: false, status: resp.status, data, erro: (data && (data.error || data.message)) || ('Erro ' + resp.status) };
    return { ok: true, status: resp.status, data };
  } catch (e) {
    return { ok: false, status: 0, data: null, erro: 'Sem resposta da função do Mercado Livre (' + (e.message || e) + ').' };
  }
}
const mlDataHora = (iso) => { if (!iso) return '—'; const d = new Date(iso); return isNaN(d) ? '—' : d.toLocaleDateString('pt-BR') + ' ' + d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }); };
const mlData = (iso) => { if (!iso) return '—'; const d = new Date(iso); return isNaN(d) ? '—' : d.toLocaleDateString('pt-BR'); };
const mlSkuNorm = (s) => String(s == null ? '' : s).trim().toUpperCase();

// as lojas do ML com o estado da conexão (sem token: a função só devolve conectado/apelido/vencimento)
function useMlStatus(reloadKey) {
  const [st, setSt] = React.useState({ lendo: true, lojas: [], erro: '' });
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      const r = await mlChamar('/status');
      if (!vivo) return;
      setSt(r.ok ? { lendo: false, lojas: (r.data && r.data.lojas) || [], erro: '' } : { lendo: false, lojas: [], erro: r.erro });
    })();
    return () => { vivo = false; };
  }, [reloadKey]);
  return st;
}

function MlAbas({ ativa, onNav }) {
  const abas = [['marketplace/mercado-livre', 'Lojas'], ['marketplace/mercado-livre/anuncios', 'Anúncios (vínculo)'], ['marketplace/mercado-livre/vendas', 'Vendas e resultado']];
  return (
    <div className="tabs">
      {abas.map(([id, rot]) => <div key={id} className={`tab ${ativa === id ? 'active' : ''}`} onClick={() => onNav(id)}>{rot}</div>)}
    </div>
  );
}

// ═════════════════════════════ Configurações › Mercado Livre ═════════════════════════════
function MlConexoesConfig() {
  const [reloadKey, setReloadKey] = React.useState(0);
  const st = useMlStatus(reloadKey);
  const [msg, setMsg] = React.useState('');
  const [ocupado, setOcupado] = React.useState('');
  React.useEffect(() => {
    const onMsg = (ev) => {
      if (ev.origin === location.origin && ev.data && ev.data.source === 'mercado-livre' && ev.data.event === 'connected') setReloadKey((k) => k + 1);
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, []);
  const conectar = async (l) => {
    if (window.tmNegadoAqui && window.tmNegadoAqui('editar')) { setMsg(window.tmNegadoAqui('editar')); return; }
    setOcupado(l.loja_id); setMsg('');
    const r = await mlChamar('/oauth/start?loja_id=' + encodeURIComponent(l.loja_id));
    setOcupado('');
    if (!r.ok || !r.data || !r.data.url) { setMsg('Não consegui começar a conexão: ' + (r.erro || 'sem endereço de autorização')); return; }
    const w = window.open(r.data.url, 'ml_oauth', 'width=640,height=760');
    if (!w) setMsg('O navegador bloqueou a janela do Mercado Livre. Permita janelas (pop-ups) para este site e clique em Conectar de novo.');
    else setMsg('Na janela do Mercado Livre, entre com a conta TITULAR de ' + l.nome + ' (não a de um colaborador) e autorize. Ela fecha sozinha.');
  };
  const desconectar = async (l) => {
    if (window.tmNegadoAqui && window.tmNegadoAqui('editar')) { setMsg(window.tmNegadoAqui('editar')); return; }
    if (!confirm('Desconectar ' + l.nome + ' do Mercado Livre?\n\nO acesso é revogado no ML e apagado aqui. As vendas e os anúncios já lidos (e os vínculos) ficam.')) return;
    setOcupado(l.loja_id); setMsg('');
    const r = await mlChamar('/oauth/disconnect', { method: 'POST', body: { loja_id: l.loja_id } });
    setOcupado('');
    if (!r.ok) { setMsg('Não consegui desconectar: ' + r.erro); return; }
    setMsg(r.data && r.data.aviso ? r.data.aviso : l.nome + ': desconectada, e o acesso foi revogado no Mercado Livre.');
    setReloadKey((k) => k + 1);
  };
  return (
    <div>
      <div className="section-head"><div><h3>Mercado Livre</h3>
        <div className="muted" style={{ fontSize: 12.5 }}>Cada loja autoriza o aplicativo com o login dela. O acesso é só de leitura; ele fica guardado no servidor e nunca aparece aqui.</div></div></div>
      {msg && <div className="card" style={{ padding: '10px 14px', marginBottom: 12, fontSize: 13 }}>{msg}</div>}
      <Card>
        {st.lendo ? <div style={{ padding: 16 }} className="muted">Lendo as lojas…</div>
          : st.erro ? <div style={{ padding: 16, color: 'var(--tm-danger)' }}>Não consegui ler as conexões: {st.erro}</div>
          : !st.lojas.length ? <div style={{ padding: 16 }} className="muted">Nenhuma loja com plataforma "Mercado Livre" em Marketplace › Lojas.</div>
          : (
          <table className="tbl"><thead><tr><th>Loja</th><th>Conexão</th><th>Conta no ML</th><th>Acesso vence</th><th></th></tr></thead>
            <tbody>{st.lojas.map((l) => (
              <tr key={l.loja_id}>
                <td style={{ fontWeight: 500 }}>{l.nome}</td>
                <td>{l.conectado ? <Badge tone="success">conectada</Badge> : l.precisaReconectar ? <Badge tone="danger">reconectar</Badge> : <Badge tone="neutral">não conectada</Badge>}</td>
                <td>{l.nickname || <span className="muted">—</span>}</td>
                <td>{l.expiraEm ? mlDataHora(l.expiraEm) + ' (renova sozinho)' : <span className="muted">—</span>}</td>
                <td style={{ whiteSpace: 'nowrap', textAlign: 'right' }}>
                  <Button variant={l.conectado ? 'secondary' : 'primary'} size="sm" icon="link" pede="editar" disabled={ocupado === l.loja_id} onClick={() => conectar(l)}>
                    {l.conectado ? 'Reconectar' : 'Conectar'}</Button>
                  {(l.conectado || l.precisaReconectar) && <Button variant="ghost" size="sm" icon="unlink" pede="editar" disabled={ocupado === l.loja_id} onClick={() => desconectar(l)}>Desconectar</Button>}
                </td>
              </tr>))}</tbody></table>
        )}
      </Card>
      <div className="muted" style={{ fontSize: 12, marginTop: 10 }}>
        Antes de cada conexão, saia do Mercado Livre no navegador e entre com a conta da loja certa. A mesma conta não liga em duas lojas.
      </div>
    </div>
  );
}

// ═════════════════════════════ Marketplace › Mercado Livre › Lojas ═════════════════════════════
function MlLojas({ onNav }) {
  const [reloadKey, setReloadKey] = React.useState(0);
  const st = useMlStatus(reloadKey);
  const [info, setInfo] = React.useState({});      // por loja: estado das sincronizações e contagens
  const [rodando, setRodando] = React.useState(null);
  const [msg, setMsg] = React.useState('');
  const [conf, setConf] = React.useState(null);    // { loja, mes, lendo, r, erro }
  const lojasKey = st.lojas.map((l) => l.loja_id).join(',');
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase || !lojasKey) return;
      const sb = window.tmSupabase, hoje = ymdLocal(new Date()), out = {};
      const est = await sb.from('ml_sync_estado').select('*');
      for (const id of lojasKey.split(',')) {
        const [an, sv, vd, rs] = await Promise.all([
          sb.from('ml_anuncios').select('id', { count: 'exact', head: true }).eq('loja_id', id),
          sb.from('ml_anuncios').select('id', { count: 'exact', head: true }).eq('loja_id', id).is('produto_id', null).eq('ignorado', false),
          sb.from('ml_vendas').select('id', { count: 'exact', head: true }).eq('loja_id', id).gte('data_criacao', ML_DESDE + 'T00:00:00-03:00'),
          sb.rpc('ml_resumo', { p_loja: id, p_de: ML_DESDE, p_ate: hoje }),
        ]);
        const erro = [an, sv, vd, rs, est].map((x) => x.error && (x.error.message || x.error.code)).filter(Boolean)[0] || '';
        out[id] = { erro, anuncios: an.count, semVinculo: sv.count, vendas: vd.count,
          vendasSemVinculo: rs.data && rs.data.sem_vinculo ? rs.data.sem_vinculo.vendas : null,
          estado: (est.data || []).filter((e) => e.loja_id === id) };
      }
      if (vivo) setInfo(out);
    })();
    return () => { vivo = false; };
  }, [lojasKey, reloadKey]);

  const sincronizar = async (l, tipo, modo) => {
    if (window.tmNegadoAqui && window.tmNegadoAqui('editar')) { setMsg(window.tmNegadoAqui('editar')); return; }
    setMsg(''); setRodando({ loja: l.loja_id, tipo, texto: 'começando…' });
    let ultimo = null;
    for (let i = 0; i < 300; i++) {
      const r = await mlChamar('/sync/' + tipo, { method: 'POST', body: { loja_id: l.loja_id, modo } });
      if (!r.ok) { setMsg(l.nome + ': a sincronização parou — ' + r.erro + ' Clicar de novo continua de onde parou.'); break; }
      ultimo = r.data;
      if (r.data.erro) { setMsg(l.nome + ': a sincronização parou — ' + r.data.erro + ' Clicar de novo continua de onde parou.'); break; }
      setRodando({ loja: l.loja_id, tipo, texto: tipo === 'anuncios' ? `${r.data.total_lido || 0} anúncios lidos`
        : r.data.fase === 'releitura' ? 'relendo as vendas dos últimos 90 dias…' : r.data.fase === 'reclamacoes' ? 'lendo as reclamações…'
        : `${r.data.total_lido || 0} vendas lidas${r.data.ate ? ' · até ' + mlData(r.data.ate) : ''}` });
      if (r.data.terminou) { setMsg(l.nome + ': ' + (r.data.mensagem || (tipo === 'anuncios' ? 'anúncios atualizados.' : 'vendas em dia.'))); break; }
    }
    setRodando(null); setReloadKey((k) => k + 1);
    return ultimo;
  };
  const conferir = async (l, mes) => {
    setConf({ loja: l.loja_id, nome: l.nome, mes, lendo: true });
    const r = await mlChamar('/conferir?loja_id=' + encodeURIComponent(l.loja_id) + '&mes=' + encodeURIComponent(mes));
    setConf({ loja: l.loja_id, nome: l.nome, mes, lendo: false, r: r.ok ? r.data : null, erro: r.ok ? '' : r.erro });
  };
  const mesPadrao = (() => { const d = new Date(); d.setMonth(d.getMonth() - 1); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0'); })();
  const [mesConf, setMesConf] = React.useState(mesPadrao);

  return (
    <div className="canvas">
      <PageHead title="Marketplace · Mercado Livre" sub="As lojas do Mercado Livre: conexão, sincronização e conferência. Nada aqui mexe em estoque, pedido ou financeiro." />
      <MlAbas ativa="marketplace/mercado-livre" onNav={onNav} />
      {msg && <div className="card" style={{ padding: '10px 14px', margin: '12px 0', fontSize: 13 }}>{msg}</div>}
      {st.lendo ? <div className="muted" style={{ padding: 16 }}>Lendo as lojas…</div>
        : st.erro ? <div style={{ padding: 16, color: 'var(--tm-danger)' }}>Não consegui ler as lojas: {st.erro}</div>
        : !st.lojas.length ? <div className="muted" style={{ padding: 16 }}>Nenhuma loja com plataforma "Mercado Livre" em Marketplace › Lojas.</div>
        : (
        <div style={{ display: 'grid', gap: 12, marginTop: 12 }}>
          {st.lojas.map((l) => {
            const i = info[l.loja_id] || {};
            const ev = (i.estado || []).find((e) => e.tipo === 'vendas') || {}, ea = (i.estado || []).find((e) => e.tipo === 'anuncios') || {};
            const cargaFeita = !!ev.ate;
            const meu = rodando && rodando.loja === l.loja_id;
            return (
              <Card key={l.loja_id}>
                <div style={{ padding: 14, display: 'grid', gap: 10 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                    <div>
                      <div style={{ fontWeight: 600, fontSize: 15 }}>{l.nome}</div>
                      <div className="muted" style={{ fontSize: 12.5 }}>
                        {l.conectado ? <>conectada · conta {l.nickname || '—'}</> : l.precisaReconectar ? <span style={{ color: 'var(--tm-danger)' }}>a conexão caiu — reconecte</span> : 'não conectada'}
                      </div>
                    </div>
                    <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                      {!l.conectado && <Button size="sm" icon="link" pedeEm="config/mercadolivre" onClick={() => onNav('config/mercadolivre')}>Conectar em Configurações</Button>}
                      {l.conectado && <Button size="sm" icon="refresh-cw" pede="editar" disabled={!!rodando} onClick={() => sincronizar(l, 'anuncios')}>Atualizar anúncios</Button>}
                      {l.conectado && <Button size="sm" variant="primary" icon="download" pede="editar" disabled={!!rodando}
                        onClick={() => sincronizar(l, 'vendas', cargaFeita ? 'atualizar' : 'carga')}>{cargaFeita ? 'Atualizar vendas' : 'Sincronizar vendas de 2026'}</Button>}
                    </div>
                  </div>
                  {meu && <div style={{ fontSize: 13 }}><Icon name="loader" size={14} /> {rodando.tipo === 'anuncios' ? 'Anúncios' : 'Vendas'}: {rodando.texto}</div>}
                  {i.erro && <div style={{ color: 'var(--tm-danger)', fontSize: 12.5 }}>Não consegui ler os números desta loja: {i.erro}</div>}
                  <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 10 }}>
                    <KPI label="Anúncios" value={i.anuncios == null ? '—' : String(i.anuncios)} deltaLabel={'atualizado ' + mlDataHora(ea.concluido_em)} />
                    <KPI label="Anúncios sem vínculo" value={i.semVinculo == null ? '—' : String(i.semVinculo)} deltaLabel="vincule em Anúncios" />
                    <KPI label="Vendas de 2026" value={i.vendas == null ? '—' : String(i.vendas)} deltaLabel={cargaFeita ? 'até ' + mlDataHora(ev.ate) : (ev.fase === 'carga' ? 'carga em andamento' : 'ainda não sincronizadas')} />
                    <KPI label="Vendas com item sem vínculo" value={i.vendasSemVinculo == null ? '—' : String(i.vendasSemVinculo)} deltaLabel="das que contam como venda" />
                    <KPI label="Última sincronização" value={mlData(ev.concluido_em || ev.atualizado_em)} deltaLabel={ev.erro ? 'parou: ' + ev.erro : (ev.cancelados_pela_busca != null ? `busca "cancelados": ${ev.cancelados_pela_busca}` : '')} />
                  </div>
                  {l.conectado && (
                    <div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', fontSize: 13 }}>
                      <span className="muted">Conferir mês (estimado × faturado pelo ML, só leitura):</span>
                      <input className="input" type="month" value={mesConf} onChange={(e) => setMesConf(e.target.value)} style={{ width: 150 }} />
                      <Button size="sm" variant="ghost" icon="scale" onClick={() => conferir(l, mesConf)}>Conferir mês</Button>
                    </div>
                  )}
                  {conf && conf.loja === l.loja_id && <MlConferencia conf={conf} />}
                </div>
              </Card>
            );
          })}
        </div>
      )}
    </div>
  );
}
function MlConferencia({ conf }) {
  if (conf.lendo) return <div className="muted" style={{ fontSize: 13 }}>Lendo o faturamento de {conf.mes}…</div>;
  if (conf.erro) return <div style={{ color: 'var(--tm-danger)', fontSize: 13 }}>Não consegui conferir: {conf.erro}</div>;
  const r = conf.r || {};
  return (
    <div style={{ borderTop: '1px solid var(--tm-line-1)', paddingTop: 10, fontSize: 13 }}>
      <div><b>{conf.nome} · faturamento de {conf.mes}</b> — {r.casados} pedidos casados com as vendas gravadas ({r.so_no_faturamento} só no faturamento){r.completo === false ? ' · leitura INCOMPLETA (tempo)' : ''}</div>
      <div style={{ display: 'flex', gap: 18, margin: '6px 0' }}>
        <span>Estimado (taxa + frete do vendedor): <b>R$ {brl(r.soma_estimada)}</b></span>
        <span>Faturado pelo ML: <b>R$ {brl(r.soma_faturada)}</b></span>
        <span>Diferença: <b>R$ {brl(r.diferenca)}</b></span>
      </div>
      {(r.maiores_diferencas || []).length > 0 && (
        <table className="tbl"><thead><tr><th>Pedido</th><th>Data</th><th style={{ textAlign: 'right' }}>Estimado</th><th style={{ textAlign: 'right' }}>Faturado</th><th style={{ textAlign: 'right' }}>Diferença</th><th>Composição faturada</th></tr></thead>
          <tbody>{r.maiores_diferencas.map((p) => (
            <tr key={p.order_id}><td>{p.order_id}</td><td>{mlData(p.data)}</td><td style={{ textAlign: 'right' }}>{brl(p.estimado)}</td><td style={{ textAlign: 'right' }}>{brl(p.faturado)}</td>
              <td style={{ textAlign: 'right' }}>{brl(p.diferenca)}</td><td className="muted" style={{ fontSize: 12 }}>{Object.entries(p.tipos || {}).map(([k, v]) => k + ' ' + brl(v)).join(' · ')}</td></tr>))}</tbody></table>
      )}
    </div>
  );
}

// ═════════════════════════════ Marketplace › Mercado Livre › Anúncios (vínculo) ═════════════════════════════
const ML_SEM_TOM = '__sem_tom__';
function MlAnuncios({ onNav }) {
  const st = useMlStatus(0);
  const [lojaId, setLojaId] = React.useState('');
  const [filtro, setFiltro] = React.useState('sem');
  const [busca, setBusca] = React.useState('');
  const [reloadKey, setReloadKey] = React.useState(0);
  const [dados, setDados] = React.useState({ lendo: true, rows: [], erro: '' });
  const [tons, setTons] = React.useState({ rows: [], erro: '' });
  const [rascunho, setRascunho] = React.useState({});    // por anúncio: { produtoId, tom }
  const [msg, setMsg] = React.useState('');
  const [oferta, setOferta] = React.useState(null);      // "aplicar a todas as linhas deste produto"
  const [pagina, setPagina] = React.useState(1);
  const prodHook = useProdutosList([]);
  const produtos = (prodHook.rows || []).filter((p) => p.tipo !== 'insumo');
  React.useEffect(() => { if (!lojaId && st.lojas.length) setLojaId(st.lojas[0].loja_id); }, [st.lojas.length, lojaId]);
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase || !lojaId) return;
      setDados((d) => ({ ...d, lendo: true }));
      const r = await lerTodas(() => window.tmSupabase.from('ml_anuncios').select('*').eq('loja_id', lojaId));
      if (vivo) setDados(r.error ? { lendo: false, rows: [], erro: r.error.message || String(r.error) } : { lendo: false, rows: r.data || [], erro: '' });
    })();
    return () => { vivo = false; };
  }, [lojaId, reloadKey]);
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase) return;
      const r = await lerTodas(() => window.tmSupabase.from('produto_variacoes').select('id,produto_id,nome,sku,ativo'));
      if (vivo) setTons(r.error ? { rows: [], erro: r.error.message || String(r.error) } : { rows: r.data || [], erro: '' });
    })();
    return () => { vivo = false; };
  }, []);

  const prodPorId = {}; produtos.forEach((p) => { prodPorId[p.id] = p; });
  const tomPorId = {}; tons.rows.forEach((v) => { tomPorId[v.id] = v; });
  // a sugestão: SÓ por SKU IGUAL (trim + maiúsculas, nada mais); SELLER_SKU primeiro, depois o campo interno.
  // SKU que aponta para dois cadastros é ambíguo: sem sugestão.
  const porSku = {};
  const marcar = (sku, alvo) => { const k = mlSkuNorm(sku); if (!k) return; porSku[k] = porSku[k] && porSku[k] !== alvo ? 'ambiguo' : alvo; };
  produtos.forEach((p) => marcar(p.sku, { produtoId: p.id, tom: null, rotulo: p.sku + ' · ' + (p.nome || p.name || '') }));
  tons.rows.forEach((v) => { const p = prodPorId[v.produto_id]; if (p) marcar(v.sku, { produtoId: p.id, tom: v.id, rotulo: (v.sku || p.sku) + ' · ' + (p.nome || '') + ' · ' + v.nome }); });
  const sugestaoDe = (a) => {
    for (const [campo, rot] of [['sku_anuncio', 'SELLER_SKU'], ['sku_interno', 'campo interno (seller_custom_field)']]) {
      const alvo = porSku[mlSkuNorm(a[campo])];
      if (alvo && alvo !== 'ambiguo') {
        const p = prodPorId[alvo.produtoId];
        // produto com tonalidades casado pelo SKU do PAI: falta o tom — não entra no "aceitar todas"
        const completa = !!alvo.tom || !(p && p.temVariacoes);
        return { ...alvo, campo: rot, completa };
      }
    }
    return null;
  };
  const vinculado = (a) => !!a.produto_id;
  const linhas = dados.rows.filter((a) => (filtro === 'sem' ? !vinculado(a) && !a.ignorado : filtro === 'vinculados' ? vinculado(a) : filtro === 'ignorados' ? a.ignorado : true))
    .filter((a) => { const q = tmSemAcento(busca.trim().toLowerCase()); return !q || tmSemAcento(String((a.titulo || '') + ' ' + (a.sku_anuncio || '') + ' ' + (a.sku_interno || '') + ' ' + a.item_id).toLowerCase()).includes(q); })
    .sort((x, y) => String(x.titulo || '').localeCompare(String(y.titulo || '')));
  const POR_PAGINA = 50;
  const totalPaginas = Math.max(1, Math.ceil(linhas.length / POR_PAGINA));
  const atual = Math.min(Math.max(pagina, 1), totalPaginas);
  const pageRows = linhas.slice((atual - 1) * POR_PAGINA, atual * POR_PAGINA);
  const aceitaveis = dados.rows.filter((a) => !vinculado(a) && !a.ignorado).map((a) => ({ a, s: sugestaoDe(a) })).filter((x) => x.s && x.s.completa);

  const meuId = () => (window.tmUserProfile && window.tmUserProfile.id) || null;
  const gravarVinculo = async (ids, campos) => {
    if (window.tmNegadoAqui && window.tmNegadoAqui('editar')) return { erro: window.tmNegadoAqui('editar') };
    const { data, error } = await window.tmSupabase.from('ml_anuncios').update({ ...campos, vinculado_em: campos.produto_id || campos.ignorado ? new Date().toISOString() : null,
      vinculado_por: campos.produto_id || campos.ignorado ? meuId() : null }).in('id', ids).select('id');
    if (error) return { erro: error.message || error.code };
    if ((data || []).length !== ids.length) return { erro: `gravou ${(data || []).length} de ${ids.length} — confira a permissão (marketplace editar)` };
    return { ok: true };
  };
  const salvarLinha = async (a, alvo) => {
    const tom = alvo.tom;
    const campos = { produto_id: alvo.produtoId, variacao_id: tom && tom !== ML_SEM_TOM ? tom : null, sem_tom: tom === ML_SEM_TOM, ignorado: false };
    const r = await gravarVinculo([a.id], campos);
    if (r.erro) { setMsg('Não consegui vincular "' + (a.titulo || a.item_id) + '": ' + r.erro); return; }
    setMsg('');
    // a mesma User Product em outras linhas desta loja: OFERECE (não aplica sozinho)
    const irmas = a.user_product_id ? dados.rows.filter((x) => x.id !== a.id && x.user_product_id === a.user_product_id && !vinculado(x) && !x.ignorado) : [];
    setOferta(irmas.length ? { campos, ids: irmas.map((x) => x.id), titulo: a.titulo, n: irmas.length } : null);
    setReloadKey((k) => k + 1);
  };
  const ignorar = async (a, sim) => {
    const r = await gravarVinculo([a.id], sim ? { produto_id: null, variacao_id: null, sem_tom: false, ignorado: true } : { produto_id: null, variacao_id: null, sem_tom: false, ignorado: false });
    if (r.erro) { setMsg('Não consegui gravar: ' + r.erro); return; }
    setReloadKey((k) => k + 1);
  };
  const aplicarOferta = async () => {
    const r = await gravarVinculo(oferta.ids, oferta.campos);
    setMsg(r.erro ? 'Não consegui aplicar às outras linhas: ' + r.erro : `Vínculo aplicado a mais ${oferta.n} linha(s) do mesmo produto do ML.`);
    setOferta(null); setReloadKey((k) => k + 1);
  };
  const aceitarTodas = async () => {
    if (!aceitaveis.length) return;
    if (!confirm(`Vincular ${aceitaveis.length} anúncio(s) pelas sugestões de SKU igual?\n\nSó entram as sugestões completas (SKU idêntico a um produto ou a uma tonalidade).`)) return;
    let feitas = 0; const falhas = [];
    for (const { a, s } of aceitaveis) {
      const r = await gravarVinculo([a.id], { produto_id: s.produtoId, variacao_id: s.tom || null, sem_tom: false, ignorado: false });
      if (r.erro) falhas.push((a.titulo || a.item_id) + ': ' + r.erro); else feitas++;
    }
    setMsg(`${feitas} anúncio(s) vinculados pelas sugestões.` + (falhas.length ? ` Não gravaram (${falhas.length}): ` + falhas.slice(0, 3).join(' · ') : ''));
    setReloadKey((k) => k + 1);
  };

  const prodOpts = produtos.map((p) => ({ value: p.id, label: `${p.sku} · ${p.nome || p.name || ''}` }));
  const contar = (f) => dados.rows.filter(f).length;
  return (
    <div className="canvas">
      <PageHead title="Marketplace · Mercado Livre" sub="Vínculo anúncio ↔ produto, por loja: é ele que dá o custo (e, na Fase 1b, a baixa de estoque)." />
      <MlAbas ativa="marketplace/mercado-livre/anuncios" onNav={onNav} />
      <div className="filter-bar" style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', margin: '12px 0' }}>
        <select className="input" value={lojaId} onChange={(e) => { setLojaId(e.target.value); setPagina(1); }} style={{ width: 240 }}>
          {st.lojas.map((l) => <option key={l.loja_id} value={l.loja_id}>{l.nome}</option>)}
        </select>
        {[['sem', 'Sem vínculo', contar((a) => !vinculado(a) && !a.ignorado)], ['vinculados', 'Vinculados', contar(vinculado)], ['ignorados', 'Ignorados', contar((a) => a.ignorado)], ['todos', 'Todos', dados.rows.length]].map(([id, rot, n]) => (
          <Chip key={id} active={filtro === id} onClick={() => { setFiltro(id); setPagina(1); }}>{rot} ({n})</Chip>))}
        <input className="input" placeholder="Buscar título, SKU ou MLB…" value={busca} onChange={(e) => { setBusca(e.target.value); setPagina(1); }} style={{ width: 260 }} />
        <div style={{ flex: 1 }} />
        <Button size="sm" variant="primary" icon="wand-2" pede="editar" disabled={!aceitaveis.length} onClick={aceitarTodas}>Aceitar todas as sugestões por SKU ({aceitaveis.length})</Button>
      </div>
      {msg && <div className="card" style={{ padding: '10px 14px', marginBottom: 12, fontSize: 13 }}>{msg}</div>}
      {oferta && (
        <div className="card" style={{ padding: '10px 14px', marginBottom: 12, fontSize: 13, display: 'flex', gap: 10, alignItems: 'center' }}>
          <span>"{oferta.titulo}" é o mesmo produto do ML de mais {oferta.n} linha(s) desta loja (mesmo User Product). Aplicar o mesmo vínculo a elas?</span>
          <Button size="sm" variant="primary" pede="editar" onClick={aplicarOferta}>Aplicar a todas as linhas deste produto</Button>
          <Button size="sm" variant="ghost" onClick={() => setOferta(null)}>Não</Button>
        </div>
      )}
      {tons.erro && <div style={{ color: 'var(--tm-danger)', fontSize: 12.5, marginBottom: 8 }}>Não consegui ler as tonalidades ({tons.erro}): produtos com tom só aceitam "Tom não informado" até recarregar.</div>}
      <Card>
        {dados.lendo ? <div className="muted" style={{ padding: 16 }}>Lendo os anúncios…</div>
          : dados.erro ? <div style={{ padding: 16, color: 'var(--tm-danger)' }}>Não consegui ler os anúncios: {dados.erro}</div>
          : !dados.rows.length ? <div className="muted" style={{ padding: 16 }}>Nenhum anúncio lido desta loja ainda. Em Lojas, clique em "Atualizar anúncios" (e as vendas trazem os anúncios que venderam).</div>
          : (
          <table className="tbl"><thead><tr><th>Anúncio</th><th>SKU no ML</th><th>Vínculo</th><th></th></tr></thead>
            <tbody>{pageRows.map((a) => {
              const s = !vinculado(a) && !a.ignorado ? sugestaoDe(a) : null;
              const rz = rascunho[a.id] || {};
              const pId = rz.produtoId !== undefined ? rz.produtoId : (s ? s.produtoId : '');
              const p = prodPorId[pId];
              const tomEsc = rz.tom !== undefined ? rz.tom : (s && s.tom ? s.tom : '');
              const precisaTom = !!(p && p.temVariacoes);
              const tonsDoProd = tons.rows.filter((v) => v.produto_id === pId && v.ativo !== false);
              const pronto = !!pId && (!precisaTom || !!tomEsc);
              const prodAtual = prodPorId[a.produto_id], tomAtual = tomPorId[a.variacao_id];
              return (
                <tr key={a.id}>
                  <td style={{ maxWidth: 380 }}>
                    <div style={{ fontWeight: 500 }}>{a.titulo || '(sem título)'}</div>
                    <div className="muted" style={{ fontSize: 12 }}>{a.item_id}{a.variation_id ? ' · variação ' + a.variation_id : ''}{a.atributos_resumo ? ' · ' + a.atributos_resumo : ''}
                      {a.status ? ' · ' + a.status : ''}{a.origem === 'venda' ? ' · visto só numa venda' : ''}</div>
                  </td>
                  <td style={{ fontSize: 12.5 }}>{a.sku_anuncio || <span className="muted">—</span>}{a.sku_interno && a.sku_interno !== a.sku_anuncio ? <div className="muted">interno: {a.sku_interno}</div> : null}</td>
                  <td style={{ minWidth: 340 }}>
                    {a.ignorado ? <Badge tone="neutral">ignorado (não é produto do app)</Badge>
                      : vinculado(a) ? <span>{prodAtual ? prodAtual.sku + ' · ' + (prodAtual.nome || '') : 'produto vinculado'}{tomAtual ? ' · ' + tomAtual.nome : ''}{a.sem_tom ? <Badge tone="warning">tom não informado</Badge> : null}</span>
                      : (
                      <div style={{ display: 'grid', gap: 6 }}>
                        {s && <div style={{ fontSize: 12 }}><Badge tone="info">sugestão</Badge> SKU igual ({s.campo}): {s.rotulo}{s.completa ? '' : ' — escolha o tom'}</div>}
                        <SearchableSelect value={pId || ''} options={prodOpts} placeholder="Escolha o produto…" onChange={(v) => setRascunho((r) => ({ ...r, [a.id]: { produtoId: v, tom: '' } }))} />
                        {precisaTom && (
                          <select className="input" value={tomEsc} onChange={(e) => setRascunho((r) => ({ ...r, [a.id]: { produtoId: pId, tom: e.target.value } }))}>
                            <option value="">Escolha a tonalidade…</option>
                            {tonsDoProd.map((v) => <option key={v.id} value={v.id}>{v.nome}{v.sku ? ' · ' + v.sku : ''}</option>)}
                            <option value={ML_SEM_TOM}>Tom não informado — definir depois</option>
                          </select>)}
                      </div>)}
                  </td>
                  <td style={{ whiteSpace: 'nowrap', textAlign: 'right' }}>
                    {!vinculado(a) && !a.ignorado && <Button size="sm" variant="primary" pede="editar" disabled={!pronto} onClick={() => salvarLinha(a, { produtoId: pId, tom: tomEsc })}>Vincular</Button>}
                    {!vinculado(a) && !a.ignorado && <Button size="sm" variant="ghost" pede="editar" onClick={() => ignorar(a, true)}>Ignorar</Button>}
                    {(vinculado(a) || a.ignorado) && <Button size="sm" variant="ghost" icon="undo-2" pede="editar" onClick={() => ignorar(a, false)}>Desfazer</Button>}
                  </td>
                </tr>);
            })}</tbody></table>
        )}
        {linhas.length > POR_PAGINA && <FooterPager total={linhas.length} perPage={POR_PAGINA} page={atual} onPage={setPagina} />}
      </Card>
    </div>
  );
}

// ═════════════════════════════ Marketplace › Mercado Livre › Vendas e resultado ═════════════════════════════
const ML_PERIODOS = [['mes', 'Este mês'], ['mes_ant', 'Mês anterior'], ['30d', 'Últimos 30 dias'], ['90d', 'Últimos 90 dias'], ['ano', 'Este ano'], ['custom', 'Personalizado']];
function MlVendas({ onNav }) {
  const st = useMlStatus(0);
  const [lojaId, setLojaId] = React.useState('');
  const [periodId, setPeriodId] = React.useState('ano');
  const [cStart, setCStart] = React.useState(''), [cEnd, setCEnd] = React.useState('');
  const [reloadKey, setReloadKey] = React.useState(0);
  const [resumo, setResumo] = React.useState({ lendo: true, r: null, erro: '' });
  const [lista, setLista] = React.useState({ lendo: true, rows: [], itens: [], total: 0, erro: '' });
  const [custos, setCustos] = React.useState({ maps: null, erro: '' });
  const [pagina, setPagina] = React.useState(1);
  const prodHook = useProdutosList([]);
  const per = calcPeriod(periodId, cStart, cEnd);
  const periodoKey = per.key;
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase) return;
      const sb = window.tmSupabase;
      const [p, v, kits] = await Promise.all([lerTodas(() => sb.from('produtos').select('id,sku,custo,eh_kit,preco_kit_custo')),
        lerTodas(() => sb.from('produto_variacoes').select('id,custo')), carregarCustoKits()]);
      if (!vivo) return;
      const erro = (p.error || v.error) ? ((p.error || v.error).message || 'leitura') : '';
      setCustos({ maps: erro ? null : buildCustoMaps(p.data || [], v.data || [], kits), erro });
    })();
    return () => { vivo = false; };
  }, []);
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase) return;
      setResumo((x) => ({ ...x, lendo: true }));
      const r = await window.tmSupabase.rpc('ml_resumo', { p_loja: lojaId || null, p_de: ymdLocal(per.start), p_ate: ymdLocal(per.end) });
      if (vivo) setResumo(r.error ? { lendo: false, r: null, erro: r.error.message || r.error.code } : { lendo: false, r: r.data, erro: '' });
    })();
    return () => { vivo = false; };
  }, [lojaId, periodoKey, reloadKey]);   // eslint-disable-line
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase) return;
      const sb = window.tmSupabase;
      let q = sb.from('ml_vendas').select('id,loja_id,order_id,pack_id,data_criacao,status,marcador,conta_como_venda,total_itens,reembolsado,taxa_ml,frete_vendedor,liquido_estimado,reclamacao', { count: 'exact' })
        .gte('data_criacao', per.start.toISOString()).lte('data_criacao', per.end.toISOString()).order('data_criacao', { ascending: false });
      if (lojaId) q = q.eq('loja_id', lojaId);
      const r = await q.range((pagina - 1) * 50, pagina * 50 - 1);
      if (!vivo) return;
      if (r.error) { setLista({ lendo: false, rows: [], itens: [], total: 0, erro: r.error.message || r.error.code }); return; }
      const ids = (r.data || []).map((x) => x.id);
      const it = ids.length ? await sb.from('ml_venda_itens_resolvidos').select('venda_id,titulo,qty,preco_unit,produto_id,variacao_id,sem_tom,ignorado').in('venda_id', ids) : { data: [] };
      if (vivo) setLista({ lendo: false, rows: r.data || [], itens: it.data || [], total: r.count || 0, erro: it.error ? 'itens: ' + (it.error.message || it.error.code) : '' });
    })();
    return () => { vivo = false; };
  }, [lojaId, periodoKey, pagina, reloadKey]);   // eslint-disable-line

  const prodPorId = {}; (prodHook.rows || []).forEach((p) => { prodPorId[p.id] = p; });
  const R = resumo.r || {};
  // o custo dos vinculados pela MESMA cascata do app; o que não tem custo é contado, nunca vira zero calado
  let custo = 0, brutoSemCusto = 0, linhasSemCusto = 0;
  (R.por_produto || []).forEach((l) => {
    if (!custos.maps) return;
    const c = custoDoItemPedido({ produto_id: l.produto_id, variacao_id: l.variacao_id }, custos.maps);
    if (c.fonte === 'semcusto') { brutoSemCusto += Number(l.bruto) || 0; linhasSemCusto++; return; }
    custo += (Number(c.custo) || 0) * (Number(l.qtd) || 0);
  });
  const semVinc = R.sem_vinculo || {};
  const lucro = (Number(R.liquido) || 0) - custo;
  const incompleto = (Number(semVinc.bruto) || 0) > 0 || brutoSemCusto > 0 || !custos.maps;
  const itensDe = (id) => lista.itens.filter((x) => x.venda_id === id);
  const STATUS = { paid: 'paga', partially_refunded: 'reembolso parcial', cancelled: 'cancelada', confirmed: 'não paga / não concretizada', invalid: 'inválida', pending_cancel: 'cancelando',
    payment_required: 'aguardando pagamento', payment_in_process: 'pagamento em análise', partially_paid: 'paga em parte' };
  const lojaNome = (id) => { const l = st.lojas.find((x) => x.loja_id === id); return l ? l.nome : ''; };
  return (
    <div className="canvas">
      <PageHead title="Marketplace · Mercado Livre" sub="Vendas e resultado por loja. Isolado: não entra no DRE, no fluxo de caixa nem em recebível nesta fase." />
      <MlAbas ativa="marketplace/mercado-livre/vendas" onNav={onNav} />
      <div className="filter-bar" style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', margin: '12px 0' }}>
        <select className="input" value={lojaId} onChange={(e) => { setLojaId(e.target.value); setPagina(1); }} style={{ width: 240 }}>
          <option value="">Todas as lojas do ML</option>
          {st.lojas.map((l) => <option key={l.loja_id} value={l.loja_id}>{l.nome}</option>)}
        </select>
        <select className="input" value={periodId} onChange={(e) => { setPeriodId(e.target.value); setPagina(1); }} style={{ width: 180 }}>
          {ML_PERIODOS.map(([id, rot]) => <option key={id} value={id}>{rot}</option>)}
        </select>
        {periodId === 'custom' && <><input className="input" type="date" value={cStart} onChange={(e) => setCStart(e.target.value)} /><input className="input" type="date" value={cEnd} onChange={(e) => setCEnd(e.target.value)} /></>}
        <Button size="sm" variant="ghost" icon="refresh-cw" onClick={() => setReloadKey((k) => k + 1)}>Recarregar</Button>
      </div>
      {resumo.erro ? <div style={{ color: 'var(--tm-danger)', padding: 12 }}>Não consegui ler o resumo: {resumo.erro}</div> : (
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 10 }}>
          <KPI label="Vendas" value={resumo.lendo ? '…' : String(R.vendas || 0)} deltaLabel="pagas, sem as marcadas" />
          <KPI label="Bruto" value={'R$ ' + brl(R.bruto)} deltaLabel={Number(R.reembolsado) ? 'reembolsado R$ ' + brl(R.reembolsado) : ''} />
          <div title={ML_TAXA_DICA}><KPI label="Taxa ML (estimada)" value={'R$ ' + brl(R.taxa_ml)} deltaLabel="estimada ⓘ" /></div>
          <KPI label="Frete do vendedor" value={'R$ ' + brl(R.frete_vendedor)} deltaLabel={R.frete_nao_lido ? R.frete_nao_lido + ' sem frete lido' : 'comprador: R$ ' + brl(R.frete_comprador) + ' (fora)'} />
          <KPI label="Líquido (estimado)" value={'R$ ' + brl(R.liquido)} deltaLabel="bruto − reembolso − taxa − frete" />
          <KPI label="Custo (vinculados)" value={custos.erro ? '—' : 'R$ ' + brl(custo)} deltaLabel={custos.erro ? 'custo não lido' : linhasSemCusto ? linhasSemCusto + ' produto(s) sem custo' : 'cascata do app'} />
          <KPI label={'Lucro bruto' + (incompleto ? '*' : '')} value={custos.erro ? '—' : 'R$ ' + brl(lucro)} deltaLabel="líquido − custo" />
        </div>
      )}
      {!resumo.erro && (
        <div className="muted" style={{ fontSize: 12.5, margin: '10px 0 14px', display: 'grid', gap: 4 }}>
          {(Number(semVinc.bruto) > 0 || brutoSemCusto > 0) && <div>* Itens sem vínculo: <b>R$ {brl(semVinc.bruto)}</b> do bruto ({semVinc.itens || 0} linha(s), {semVinc.anuncios || 0} anúncio(s)) sem custo — vincule em Anúncios.
            {brutoSemCusto > 0 ? <> Vinculados sem custo cadastrado: R$ {brl(brutoSemCusto)}.</> : null}</div>}
          {R.ignorados && Number(R.ignorados.bruto) > 0 && <div>Anúncios ignorados (não são produto do app): R$ {brl(R.ignorados.bruto)} do bruto, sem custo.</div>}
          {R.fora_da_soma && Object.keys(R.fora_da_soma).length > 0 && <div>Fora da soma: {Object.entries(R.fora_da_soma).map(([k, n]) => (k === 'fraude' ? 'risco de fraude' : k === 'cancelada_com_devolucao' ? 'cancelada com devolução' : (STATUS[k] || k)) + ' ' + n).join(' · ')}.</div>}
          <div>A taxa é a do pedido (estimada): {ML_TAXA_DICA}</div>
        </div>
      )}
      <Card>
        {lista.lendo ? <div className="muted" style={{ padding: 16 }}>Lendo as vendas…</div>
          : lista.erro && !lista.rows.length ? <div style={{ padding: 16, color: 'var(--tm-danger)' }}>Não consegui ler as vendas: {lista.erro}</div>
          : !lista.rows.length ? <div className="muted" style={{ padding: 16 }}>Nenhuma venda no período. Se a loja ainda não foi sincronizada, vá em Lojas.</div>
          : (
          <table className="tbl"><thead><tr><th>Data</th><th>Pedido</th><th>Status</th><th>Itens</th><th style={{ textAlign: 'right' }}>Bruto</th>
            <th style={{ textAlign: 'right' }} title={ML_TAXA_DICA}>Taxa: estimada</th><th style={{ textAlign: 'right' }}>Frete vend.</th><th style={{ textAlign: 'right' }}>Líquido</th></tr></thead>
            <tbody>{lista.rows.map((v) => (
              <tr key={v.id} style={v.conta_como_venda ? undefined : { opacity: 0.6 }}>
                <td>{mlDataHora(v.data_criacao)}</td>
                <td><div>{v.order_id}</div>{!lojaId && <div className="muted" style={{ fontSize: 11.5 }}>{lojaNome(v.loja_id)}</div>}</td>
                <td>
                  <Badge tone={v.conta_como_venda ? 'success' : 'neutral'}>{STATUS[v.status] || v.status}</Badge>
                  {v.marcador && <Badge tone="danger">{v.marcador === 'fraude' ? 'risco de fraude' : 'cancelada com devolução'}</Badge>}
                  {v.reclamacao && <div className="muted" style={{ fontSize: 11.5 }}>reclamação {v.reclamacao}</div>}
                </td>
                <td style={{ fontSize: 12.5 }}>{itensDe(v.id).map((it, k) => {
                  const p = prodPorId[it.produto_id];
                  return <div key={k}>{it.qty}× {it.titulo || '—'} <span className="muted">→ {it.ignorado ? 'ignorado' : p ? p.sku + (it.sem_tom ? ' (tom não informado)' : '') : 'sem vínculo'}</span></div>;
                })}</td>
                <td style={{ textAlign: 'right' }}>{brl(v.total_itens)}</td>
                <td style={{ textAlign: 'right' }} title={ML_TAXA_DICA}>{brl(v.taxa_ml)}</td>
                <td style={{ textAlign: 'right' }}>{v.frete_vendedor == null ? <span className="muted">não lido</span> : brl(v.frete_vendedor)}</td>
                <td style={{ textAlign: 'right' }}>{brl(v.liquido_estimado)}</td>
              </tr>))}</tbody></table>
        )}
        {lista.total > 50 && <FooterPager total={lista.total} perPage={50} page={pagina} onPage={setPagina} />}
      </Card>
    </div>
  );
}

Object.assign(window, { MlConexoesConfig, MlLojas, MlAnuncios, MlVendas });
