/* global React, Icon, Button, Card, PageHead, EstadoDados, brl, fmtDate, ymdLocal, useMktFiltros, mktTituloDaTela, lerTodas,
   emLotes, lerResultadoEstimado, mktResultadoEstimado, mktResultadoCobertura, mktEhPedidoML, mktDiasEntre, MKT_SEM_FECHAMENTO_DIAS,
   MKT_FECHAMENTOS_JANELA_DIAS, MP_REL_FILA_CATEGORIA, mktRendimentosFaltaTabela, REL_LIMITE_RECEITA_SEM_CUSTO */
// ============================================================
// Marketplace_Visao.jsx — Marketplace › Visão geral de DECISÃO (Lote E2, 20/10/2026 — a palavra do PM)
//
// Tudo pela DATA DA VENDA (decisão 1): das vendas do período, o que já entrou, o que falta entrar e o resultado delas. O dinheiro
// pelo dia em que entrou é o Financeiro (o DRE de caixa). Os números saem da régua ÚNICA (`mktResultadoEstimado`, db.js) — a mesma
// do cartão do Dashboard, de Relatórios › Financeiro e do Faturamento por canal: nenhuma conta aqui.
// O que saiu (era o Painel provisório do "repasse"): "Já repassado", "Taxa % (sobre repassado)", "Margem sobre o já repassado" e a
// tabela "Marketplace por plataforma" — o Mercado Livre não tem repasse desde a F2a, e a taxa que falta é ESTIMADA (decisão 3).
// "Precisa da sua atenção": as pendências do marketplace num lugar só, com o link de cada uma; o dinheiro dos fechamentos pelo
// período do filtro, o resto "agora"; cada item só para quem enxerga o destino (a pergunta do menu, `dashAtalhoVisivel`).
// ============================================================

const MKV_DESDE = '2026-01-01';   // o começo das vendas do ML no app — os meses de extrato contam a partir da âncora da conta

function mkvPct(n) { return (Number(n) || 0).toFixed(1).replace('.', ',') + '%'; }
function mkvPlural(n, um, varios) { return (Number(n) || 0).toLocaleString('pt-BR') + ' ' + (n === 1 ? um : varios); }

// OS QUATRO CARTÕES, pura (a PROVA_visao-geral-mkt.js roda esta): o valor e a frase de cada um. Valor `null` = "—", com o motivo.
function mktVisaoCartoes(res) {
  const r = res || {}, m = r.mkt || {}, lojas = r.lojas || [];
  const ml = lojas.filter((a) => a.ml), fe = lojas.filter((a) => !a.ml);
  const nML = ml.reduce((s, a) => s + (a.n || 0), 0), nFe = fe.reduce((s, a) => s + (a.n || 0), 0);
  const soma = (l, k) => Math.round(l.reduce((s, a) => s + (Number(a[k]) || 0), 0) * 100) / 100;
  const temML = nML > 0, temFe = nFe > 0;
  const semVisaoML = temML && !r.recebidoLido;
  // cada cartão abre os registros dele, com o MESMO filtro (briefing § 3; achado na validação final, 21/10/2026): o filtro é
  // o da entrada do Marketplace, guardado na aba — a área de destino o lê igual. O resultado leva à tabela "Por loja", logo abaixo.
  const vendas = { rotulo: 'Vendas brutas', valor: m.vendas || 0, rota: 'marketplace/vendas', acao: 'Ver as vendas',
    frase: [temML ? mkvPlural(nML, 'venda do Mercado Livre', 'vendas do Mercado Livre') : null,
      temFe ? mkvPlural(nFe, 'fechamento da Shopee/TikTok', 'fechamentos da Shopee/TikTok') : null].filter(Boolean).join(' · ') || 'nenhuma venda no período' };
  // RECEBIDO: o ML pela visão (o que o Mercado Pago já liberou DESSAS vendas, com a parte que pagou o empréstimo — decisão 2); a
  // Shopee/TikTok pelo dinheiro ligado ao fechamento (Lote D), ou, no mês declarado, o liberado da declaração (fora do caixa do app).
  const partesRec = [];
  if (temML) {
    if (semVisaoML) partesRec.push('o recebido do Mercado Livre pela data da venda ainda não existe no banco (falta uma atualização do banco)');
    else {
      const emp = soma(ml, 'emprestimo'), nl = soma(ml, 'pagNaoLidos'), nlv = soma(ml, 'pagNaoLidosValor');
      partesRec.push('Mercado Livre: R$ ' + brl(soma(ml, 'recebido')) + ' que o Mercado Pago já liberou dessas vendas'
        + (emp > 0 ? ' (R$ ' + brl(emp) + ' pagaram o empréstimo)' : ''));
      if (nl > 0) partesRec.push('falta ler o pagamento de ' + mkvPlural(nl, 'venda', 'vendas') + ' (R$ ' + brl(nlv) + ')');
    }
  }
  if (temFe) {
    const lig = soma(fe, 'recebido') - soma(fe, 'recebidoDeclarado'), dec = soma(fe, 'recebidoDeclarado'), ant = soma(fe, 'anteriorAoCaixa');
    partesRec.push('Shopee/TikTok: R$ ' + brl(lig) + ' de dinheiro ligado ao fechamento'
      + (dec > 0 ? ' · R$ ' + brl(dec) + ' liberados nos meses declarados (pela declaração, fora do caixa do app)' : '')
      + (ant > 0 ? ' · R$ ' + brl(ant) + ' de fechamentos anteriores ao caixa do app (fora da conta)' : ''));
  }
  const recebido = { rotulo: 'Recebido', valor: semVisaoML ? null : (m.recebido || 0), rota: 'marketplace/recebimentos', acao: 'Ver os recebimentos', frase: partesRec.join(' · ') || 'nada no período' };
  // A RECEBER: o ML a liberar e retido (mediação); a Shopee/TikTok o que falta do fechamento depois da taxa (a ESTIMADA, quando falta
  // a declaração — decisão 5: no valor e na frase)
  const partesAr = [];
  if (temML && !semVisaoML) {
    const lib = soma(ml, 'aLiberar'), ret = soma(ml, 'retido');
    partesAr.push('Mercado Livre: R$ ' + brl(lib) + ' a liberar' + (ret > 0 ? ' · R$ ' + brl(ret) + ' retidos (mediação)' : ''));
  }
  if (temFe) {
    const ar = Math.round((soma(fe, 'aReceber')) * 100) / 100, est = soma(fe, 'nEstimada');
    partesAr.push('Shopee/TikTok: R$ ' + brl(ar) + (est > 0 ? ' — com a taxa estimada em ' + mkvPlural(est, 'fechamento', 'fechamentos') : ''));
  }
  const aReceber = { rotulo: 'A receber', valor: semVisaoML ? null : (m.aReceber || 0), rota: 'marketplace/recebimentos', acao: 'Ver o que falta receber', frase: partesAr.join(' · ') || 'nada a receber no período' };
  // RESULTADO ESTIMADO: nunca "lucro" — a base não tem folha, despesas fixas nem o imposto da empresa
  const cob = mktResultadoCobertura(r, 'mkt');
  const oculto = !!(r.resultado && r.resultado.oculto);
  const parcial = (m.brutoEstimado || 0) > 0.004 || (m.nFora || 0) > 0 || ((r.fatura || {}).abertos || []).length > 0 || (m.pagNaoLidos || 0) > 0;
  const resultado = { rotulo: 'Resultado estimado', valor: oculto ? null : (m.resultado || 0), marca: r.resultado ? r.resultado.marca : '', parcial,
    ancora: 'mkv-por-loja', acao: 'Ver por loja',
    frase: (oculto ? 'mais de ' + REL_LIMITE_RECEITA_SEM_CUSTO + '% da receita vem de item sem custo: cadastre o custo para ver o resultado · ' : '')
      + 'vendas − custo − taxas − fatura do ML, pela data da venda · sem folha, despesas fixas e impostos', cobertura: cob };
  return { vendas, recebido, aReceber, resultado };
}

// "PRECISA DA SUA ATENÇÃO", pura (a PROVA roda esta): de `d` (as leituras de agora, `mktLerAtencao`) e `res`/`fech` (o período do
// filtro), a lista ordenada — dinheiro, classificação, operação. Item só com número > 0 (ou "sem número", os conflitos), e só se a
// pessoa enxerga o destino (`ctx.visivel(rota)`). Leitura que falhou vira o item "não consegui ler …", nunca zero.
function mktAtencaoItens(d, res, fech, ctx) {
  const c = ctx || {}, visivel = typeof c.visivel === 'function' ? c.visivel : (() => true);
  const x = d || {}, itens = [];
  const add = (it) => { if (it && visivel(it.rota)) itens.push(it); };
  const falha = (id, oQue, rota, grupo) => add({ id, grupo, tom: 'danger', texto: 'Não consegui ler ' + oQue + ' — confira em seguida', rota, acao: 'Abrir' });
  const n = (v) => Number(v) || 0;
  // ── dinheiro ──
  if (x.sincErro) falha('sinc', 'a sincronização do Mercado Livre', 'marketplace/lojas', 'dinheiro');
  else if (n(x.sincFalhas) + n(x.sincEspera) > 0) add({ id: 'sinc', grupo: 'dinheiro', tom: 'danger',
    texto: (n(x.sincFalhas) ? mkvPlural(n(x.sincFalhas), 'loja do ML com a última sincronização parada', 'lojas do ML com a última sincronização parada') : '')
      + (n(x.sincFalhas) && n(x.sincEspera) ? ' · ' : '') + (n(x.sincEspera) ? mkvPlural(n(x.sincEspera), 'loja com o automático em espera (falhas seguidas)', 'lojas com o automático em espera (falhas seguidas)') : ''),
    rota: 'marketplace/lojas', acao: 'Ver as lojas' });
  if (n(x.dinheiroRecusado) > 0) add({ id: 'identidade', grupo: 'dinheiro', tom: 'danger',
    texto: mkvPlural(n(x.dinheiroRecusado), 'conta do Mercado Pago recusada pela identidade: o dinheiro dela não entra no caixa', 'contas do Mercado Pago recusadas pela identidade: o dinheiro delas não entra no caixa'),
    rota: 'marketplace/recebimentos', acao: 'Ver em Recebimentos' });
  if (x.extratoErro) falha('extrato', 'os extratos do Mercado Pago', 'marketplace/recebimentos/extrato-mp', 'dinheiro');
  else if ((x.mesesSemExtrato || []).length) add({ id: 'extrato', grupo: 'dinheiro', tom: 'warning',
    texto: mkvPlural(x.mesesSemExtrato.length, 'mês sem o extrato do Mercado Pago', 'meses sem o extrato do Mercado Pago') + ' (' + x.mesesSemExtrato.slice(0, 4).join(', ') + (x.mesesSemExtrato.length > 4 ? ' e mais ' + (x.mesesSemExtrato.length - 4) : '') + ')',
    rota: 'marketplace/recebimentos/extrato-mp', acao: 'Importar o extrato' });
  if (x.filaErro) falha('fila', 'a fila do extrato', 'marketplace/recebimentos/extrato-mp', 'dinheiro');
  else if (n(x.fila) > 0) add({ id: 'fila', grupo: 'dinheiro', tom: 'warning', texto: mkvPlural(n(x.fila), 'linha do extrato a classificar', 'linhas do extrato a classificar'),
    rota: 'marketplace/recebimentos/extrato-mp', acao: 'Classificar' });
  if (x.emprestimoErro) falha('emprestimo', 'o empréstimo do Mercado Pago', 'financeiro/saldos', 'dinheiro');
  else if (n(x.semContrato) > 0) add({ id: 'emprestimo', grupo: 'dinheiro', tom: 'warning',
    texto: mkvPlural(n(x.semContrato), 'empréstimo do Mercado Pago sem contrato: os juros não se separam', 'empréstimos do Mercado Pago sem contrato: os juros não se separam'),
    rota: 'financeiro/saldos', acao: 'Informar o contrato' });
  if (x.faturaErro) falha('fatura', 'a fatura do Mercado Livre', 'marketplace/lojas', 'dinheiro');
  else if ((x.faturaClassificar || []).length) add({ id: 'fatura', grupo: 'dinheiro', tom: 'warning',
    texto: 'Fatura do ML: ' + mkvPlural(x.faturaClassificar.length, 'código a classificar', 'códigos a classificar') + ' — '
      + x.faturaClassificar.slice(0, 3).map((f) => f.codigos + ' (R$ ' + brl(f.valor) + ', ' + f.mes.slice(5, 7) + '/' + f.mes.slice(0, 4) + ', ' + f.loja + ')').join(' · '),
    rota: 'marketplace/lojas', acao: 'Ver a fatura da loja' });
  // os do PERÍODO do filtro (a régua): a taxa estimada, o fechamento sem dinheiro e o sem custo
  if (res) {
    const pp = res.porPedido || {}, ids = Object.keys(pp);
    const est = ids.filter((id) => pp[id].fonte === 'estimada'), fora = ids.filter((id) => pp[id].fonte === 'fora');
    const vs = (l) => l.reduce((s, id) => s + (pp[id].vendas || 0), 0);
    if (est.length) add({ id: 'taxa', grupo: 'dinheiro', periodo: true, tom: 'warning',
      texto: mkvPlural(est.length, 'fechamento com a taxa ESTIMADA', 'fechamentos com a taxa ESTIMADA') + ' (R$ ' + brl(vs(est)) + ' de vendas): falta a declaração da Shopee',
      rota: 'marketplace/recebimentos/declaracoes', acao: 'Importar a declaração' });
    if (fora.length) add({ id: 'fora', grupo: 'dinheiro', periodo: true, tom: 'danger',
      texto: mkvPlural(fora.length, 'venda sem taxa conhecida nem referência', 'vendas sem taxa conhecida nem referência') + ' (R$ ' + brl(vs(fora)) + '), fora do resultado',
      rota: 'marketplace/recebimentos', acao: 'Ver em Recebimentos' });
    if (fech) {
      const semDin = ids.filter((id) => pp[id].canal === 'fech' && fech[id] && fech[id].sit === 'sem_dinheiro');
      const antigo = ids.filter((id) => pp[id].canal === 'fech' && fech[id] && fech[id].sit === 'antigo_conferir');
      if (semDin.length || antigo.length) add({ id: 'dinheiro', grupo: 'dinheiro', periodo: true, tom: 'warning',
        texto: [semDin.length ? mkvPlural(semDin.length, 'fechamento sem dinheiro ligado', 'fechamentos sem dinheiro ligado') + ' (R$ ' + brl(vs(semDin)) + ')' : '',
          antigo.length ? mkvPlural(antigo.length, 'do registro antigo a conferir', 'do registro antigo a conferir') : ''].filter(Boolean).join(' · '),
        rota: 'marketplace/recebimentos', acao: 'Registrar recebimento' });
    }
  }
  if (x.semContaErro) falha('semconta', 'os títulos sem conta', 'financeiro/sem-conta', 'dinheiro');
  else if (n(x.semContaCr) + n(x.semContaCp) > 0) add({ id: 'semconta', grupo: 'dinheiro', tom: 'warning',
    texto: 'Títulos do marketplace sem conta: ' + [n(x.semContaCr) ? n(x.semContaCr) + ' a receber (R$ ' + brl(x.semContaCrValor) + ')' : '',
      n(x.semContaCp) ? n(x.semContaCp) + ' a pagar (R$ ' + brl(x.semContaCpValor) + ')' : ''].filter(Boolean).join(' e '),
    rota: 'financeiro/sem-conta', acao: 'Vincular a conta' });
  // ── classificação ──
  if (x.anunciosErro) falha('anuncios', 'os anúncios sem vínculo', 'marketplace/anuncios', 'classificacao');
  else if (n(x.anunciosSemVinculo) > 0) add({ id: 'anuncios', grupo: 'classificacao', tom: 'warning',
    texto: mkvPlural(n(x.anunciosSemVinculo), 'anúncio sem vínculo', 'anúncios sem vínculo') + (x.anunciosPorLoja ? ' (' + x.anunciosPorLoja + ')' : ''),
    rota: 'marketplace/anuncios', acao: 'Vincular' });
  if (x.vendasPendErro) falha('vendasPend', 'as vendas esperando vínculo', 'marketplace/anuncios/sem-vinculo', 'classificacao');
  else if (n(x.vendasSemVinculo) > 0) add({ id: 'vendasPend', grupo: 'classificacao', tom: 'warning',
    texto: mkvPlural(n(x.vendasSemVinculo), 'venda do ML esperando vínculo (não virou pedido)', 'vendas do ML esperando vínculo (não viraram pedido)'),
    rota: 'marketplace/anuncios/sem-vinculo', acao: 'Ver as vendas' });
  if (x.temML) add({ id: 'conflitos', grupo: 'classificacao', tom: 'neutral', semNumero: true,
    texto: 'Conflitos de volume: confira os vínculos marcados em Anúncios (sem contagem aqui)', rota: 'marketplace/anuncios', acao: 'Conferir' });
  if (x.tomErro) falha('tom', 'as vendas sem tom', 'estoque/sem-tom', 'classificacao');
  else if (n(x.semTom) > 0) add({ id: 'tom', grupo: 'classificacao', tom: 'warning', texto: mkvPlural(n(x.semTom), 'venda sem tom', 'vendas sem tom'),
    rota: 'estoque/sem-tom', acao: 'Definir o tom' });
  if (res && res.custo && res.custo.pct > 0) add({ id: 'custo', grupo: 'classificacao', periodo: true, tom: res.custo.oculto ? 'danger' : 'warning',
    texto: mkvPct(res.custo.pct) + ' da receita do período vem de item sem custo (' + mkvPlural((res.custo.semCusto || 0) + (res.custo.zero || 0), 'item', 'itens') + ')',
    rota: 'produtos', acao: 'Cadastrar o custo' });
  // ── operação ──
  if (x.reclErro) falha('recl', 'as reclamações do ML', 'marketplace/vendas/mercado-livre', 'operacao');
  else if (n(x.reclamacoes) + n(x.naoEntregues) > 0) add({ id: 'recl', grupo: 'operacao', tom: 'warning',
    texto: [n(x.reclamacoes) ? mkvPlural(n(x.reclamacoes), 'reclamação aberta', 'reclamações abertas') : '',
      n(x.naoEntregues) ? mkvPlural(n(x.naoEntregues), 'venda não entregue', 'vendas não entregues') : ''].filter(Boolean).join(' · ') + ' no Mercado Livre',
    rota: 'marketplace/vendas/mercado-livre', acao: 'Ver as vendas' });
  if (n(x.semAviso) > 0) add({ id: 'aviso', grupo: 'operacao', tom: 'neutral',
    texto: 'O Mercado Livre não avisa as vendas em ' + mkvPlural(n(x.semAviso), 'loja', 'lojas') + ': elas entram pelo relógio, de 10 em 10 min',
    rota: 'marketplace/lojas', acao: 'Ver as lojas' });
  if ((x.paradas || []).length) add({ id: 'paradas', grupo: 'operacao', tom: 'neutral',
    texto: x.paradas.slice(0, 4).join(' · ') + (x.paradas.length > 4 ? ' e mais ' + (x.paradas.length - 4) : ''),
    rota: 'marketplace/vendas', acao: 'Lançar fechamento' });
  return itens;
}

// As leituras de AGORA da lista (contagens no servidor, `head`, salvo o pouco que precisa de linha). `ids` = as lojas do filtro
// (vazio = todas). Cada falha fica no seu item, e o resto aparece.
async function mktLerAtencao(ids) {
  const sb = window.tmSupabase;
  if (!sb) return {};
  const filtro = Array.isArray(ids) && ids.length ? ids : null;
  const naLoja = (q, col) => (filtro ? q.in(col || 'loja_id', filtro) : q);
  const tenta = (p) => Promise.resolve(p).then((r) => r, (e) => ({ error: e }));
  const hoje = ymdLocal(new Date());
  const desde = ymdLocal(new Date(Date.now() - MKT_FECHAMENTOS_JANELA_DIAS * 86400000));
  const fila = typeof MP_REL_FILA_CATEGORIA !== 'undefined' ? MP_REL_FILA_CATEGORIA : 'A classificar (extrato do Mercado Pago)';
  const [lojas, est, auto, emp, filaCp, filaCr, fat, rels, contas, tom, recl, naoEnt, pendV, anun, fech, scCr, scCp] = await Promise.all([
    tenta(sb.from('marketplace_lojas').select('id,nome,plataforma,situacao,conta_id')),
    tenta(naLoja(sb.from('ml_sync_estado').select('loja_id,tipo,erro,dinheiro_erro'))),
    tenta(naLoja(sb.from('ml_sync_automatico').select('loja_id,ativo,pausado_ate,falhas_seguidas,ultimo_aviso_em'))),
    tenta(sb.from('mp_emprestimos').select('id', { count: 'exact', head: true }).is('total_a_pagar', null)),
    tenta(sb.from('contas_pagar').select('id', { count: 'exact', head: true }).eq('categoria', fila)),
    tenta(sb.from('contas_receber').select('id', { count: 'exact', head: true }).like('gerado_por', 'mp|rel|%').is('natureza', null)),
    tenta(naLoja(sb.from('ml_fatura_por_mes').select('loja_id,mes,valor,codigos,sem_campo').eq('a_classificar', true))),
    tenta(sb.from('mp_relatorios').select('conta_id,mes')),
    tenta(sb.from('contas_bancarias').select('id,nome,data_saldo_inicial,mp_user_id,situacao')),
    tenta(sb.from('estoque_tom_pendente').select('*', { count: 'exact', head: true })),
    tenta(naLoja(sb.from('ml_vendas').select('id', { count: 'exact', head: true }).like('reclamacao', '%aberta%'))),
    tenta(naLoja(sb.from('ml_vendas').select('id', { count: 'exact', head: true }).eq('conta_como_venda', true).eq('envio_status', 'not_delivered'))),
    tenta(naLoja(sb.from('ml_vendas_pendentes').select('id', { count: 'exact', head: true }))),
    tenta(naLoja(sb.from('ml_anuncios').select('loja_id, ml_anuncio_itens()').eq('ignorado', false).is('ml_anuncio_itens', null))),
    tenta(lerTodas(() => naLoja(sb.from('pedidos').select('id,marketplace_loja_id,periodo_fim').eq('tipo_registro', 'fechamento').neq('status', 'Cancelado').gte('periodo_fim', desde), 'marketplace_loja_id'))),
    tenta(sb.from('contas_receber').select('id,pedido_id,status,valor,valor_recebido,data_pagamento').is('conta_id', null).not('pedido_id', 'is', null)),
    tenta(sb.from('contas_pagar').select('id,pedido_id,status,valor,data_pagamento,categoria').is('conta_id', null).eq('status', 'Pago').not('data_pagamento', 'is', null)),
  ]);
  const d = {};
  const L = (lojas.data || []);
  const passa = (id) => !filtro || filtro.includes(id);
  const nomeDe = (id) => (L.find((l) => l.id === id) || {}).nome || 'loja';
  const ml = L.filter((l) => l.plataforma === 'Mercado Livre' && l.situacao !== 'Arquivada' && passa(l.id));
  d.temML = ml.length > 0;
  // sincronização: a última parou (o erro guardado), o automático em espera, a conta recusada pela identidade
  if (est.error || auto.error) d.sincErro = true;
  else {
    const agora = Date.now();
    d.sincFalhas = new Set((est.data || []).filter((e) => e.erro && passa(e.loja_id)).map((e) => e.loja_id)).size;
    d.dinheiroRecusado = new Set((est.data || []).filter((e) => e.dinheiro_erro && passa(e.loja_id)).map((e) => e.loja_id)).size;
    d.sincEspera = (auto.data || []).filter((a) => a.ativo !== false && a.pausado_ate && Date.parse(a.pausado_ate) > agora).length;
    d.semAviso = (auto.data || []).filter((a) => a.ativo !== false && (!a.ultimo_aviso_em || agora - Date.parse(a.ultimo_aviso_em) > 24 * 3600e3)).length;
  }
  if (emp.error && !mktRendimentosFaltaTabela(emp.error)) d.emprestimoErro = true; else d.semContrato = emp.count || 0;
  if ((filaCp.error && !mktRendimentosFaltaTabela(filaCp.error)) || (filaCr.error && !mktRendimentosFaltaTabela(filaCr.error))) d.filaErro = true;
  else d.fila = (filaCp.count || 0) + (filaCr.count || 0);
  if (fat.error && !mktRendimentosFaltaTabela(fat.error)) d.faturaErro = true;
  else d.faturaClassificar = (fat.data || []).map((f) => ({ loja: nomeDe(f.loja_id), mes: String(f.mes || ''), valor: Number(f.valor) || 0, codigos: f.codigos || '?' }))
    .sort((a, b) => b.mes.localeCompare(a.mes));
  // os meses fechados sem o extrato do MP, por conta do MP (desde a âncora dela), só das contas das lojas do filtro
  if ((rels.error && !mktRendimentosFaltaTabela(rels.error)) || contas.error) d.extratoErro = true;
  else {
    const daLoja = new Set(L.filter((l) => passa(l.id) && l.conta_id).map((l) => l.conta_id));
    const importados = new Set((rels.data || []).map((r) => r.conta_id + '|' + r.mes));
    const ultimo = (() => { const [a, b] = hoje.split('-').map(Number); return new Date(Date.UTC(a, b - 2, 1)).toISOString().slice(0, 7); })();
    const meses = [];
    (contas.data || []).filter((c) => c.mp_user_id != null && c.mp_user_id !== '' && c.situacao !== 'Arquivada' && (!filtro || daLoja.has(c.id))).forEach((c) => {
      const ancora = String(c.data_saldo_inicial || MKV_DESDE).slice(0, 7);
      for (let m = ancora; m <= ultimo;) {
        if (!importados.has(c.id + '|' + m)) meses.push(m.slice(5, 7) + '/' + m.slice(0, 4) + ' ' + (c.nome || 'conta').replace('Mercado Pago - ', ''));
        const [a, b] = m.split('-').map(Number); m = new Date(Date.UTC(a, b, 1)).toISOString().slice(0, 7);
      }
    });
    d.mesesSemExtrato = meses;
  }
  if (tom.error && !mktRendimentosFaltaTabela(tom.error)) d.tomErro = true; else d.semTom = tom.count || 0;
  if (recl.error || naoEnt.error) d.reclErro = true; else { d.reclamacoes = recl.count || 0; d.naoEntregues = naoEnt.count || 0; }
  if (pendV.error) d.vendasPendErro = true; else d.vendasSemVinculo = pendV.count || 0;
  if (anun.error) d.anunciosErro = true;
  else {
    const por = {};
    (anun.data || []).forEach((a) => { por[a.loja_id] = (por[a.loja_id] || 0) + 1; });
    d.anunciosSemVinculo = (anun.data || []).length;
    d.anunciosPorLoja = Object.keys(por).map((id) => nomeDe(id).replace(' - ML', '').replace(' ML', '') + ' ' + por[id]).join(' · ');
  }
  // as lojas sem integração paradas: a mesma regra da linha da loja (`mktLojaResumo`), em tom neutro (decisão de 10/10/2026)
  if (fech.error) d.paradasErro = true;
  else {
    const ult = {};
    (fech.data || []).forEach((p) => { const k = p.marketplace_loja_id, f = String(p.periodo_fim || '').slice(0, 10); if (k && f && (!ult[k] || f > ult[k])) ult[k] = f; });
    d.paradas = L.filter((l) => l.plataforma !== 'Mercado Livre' && l.situacao !== 'Arquivada' && passa(l.id)).map((l) => {
      const f = ult[l.id];
      if (!f) return l.nome + ': nenhum fechamento em ' + MKT_FECHAMENTOS_JANELA_DIAS + ' dias';
      const dias = mktDiasEntre(f, hoje);
      return dias != null && dias > MKT_SEM_FECHAMENTO_DIAS ? l.nome + ': último até ' + fmtDate(f) + ' (há ' + dias + ' dias)' : null;
    }).filter(Boolean);
  }
  // os títulos SEM CONTA que já movimentaram dinheiro e são do marketplace (decisão 9: o resto é do Financeiro)
  if (scCr.error || scCp.error) d.semContaErro = true;
  else {
    const noCaixa = (scCr.data || []).filter((r) => window.finEntrouNoCaixa && window.finEntrouNoCaixa(r));
    const pids = [...new Set(noCaixa.map((r) => r.pedido_id).concat((scCp.data || []).map((r) => r.pedido_id)).filter(Boolean))];
    let mktIds = new Set();
    if (pids.length) {
      const pm = await tenta(emLotes(pids, (l) => sb.from('pedidos').select('id,marketplace_loja_id').in('id', l).not('marketplace_loja_id', 'is', null)));
      if (pm.error) d.semContaErro = true;
      mktIds = new Set((pm.data || []).filter((p) => passa(p.marketplace_loja_id)).map((p) => p.id));
    }
    const cr = noCaixa.filter((r) => mktIds.has(r.pedido_id));
    const cp = (scCp.data || []).filter((r) => (r.pedido_id && mktIds.has(r.pedido_id)) || (!filtro && r.categoria === 'Taxa de marketplace'));
    d.semContaCr = cr.length; d.semContaCrValor = cr.reduce((s, r) => s + (window.finValorQueEntrou ? window.finValorQueEntrou(r) : 0), 0);
    d.semContaCp = cp.length; d.semContaCpValor = cp.reduce((s, r) => s + (Number(r.valor) || 0), 0);
  }
  return d;
}

// a lista na tela: carregando · erro por item · os itens com o link
function MktAtencao({ onNav, itens, lendo, periodoNome }) {
  if (lendo) return <EstadoDados estado="carregando" titulo="Conferindo as pendências…" compacto />;
  if (!itens.length) return <EstadoDados estado="vazio" titulo="Nada pendente no marketplace" mensagem="Nenhuma das pendências que esta lista confere apareceu." compacto />;
  const icone = { danger: 'alert-octagon', warning: 'alert-triangle', neutral: 'info' };
  return (
    <ul className="mkv-atencao" aria-label="Precisa da sua atenção">
      {itens.map((it) => (
        <li key={it.id} className={'mkv-at-item mkv-at-' + it.tom}>
          <Icon name={icone[it.tom] || 'info'} size={14} />
          <span className="mkv-at-texto">{it.texto}{it.periodo ? <span className="mkv-at-per"> · {periodoNome || 'no período'}</span> : null}</span>
          <button type="button" className="fc-linkbtn" onClick={() => onNav && onNav(it.rota)}>{it.acao || 'Abrir'}</button>
        </li>
      ))}
    </ul>
  );
}

// O nome do período no rodapé do cartão: o do seletor ("Este mês"); o personalizado, pelas datas.
function mkvPeriodoNome(per) { return per && per.id === 'custom' ? (per.rotulo || 'personalizado') : ((per && per.nome) || ''); }

function MkvCartao({ c, destaque, onAbrir, periodo }) {
  return (
    <div className={'mkv-cartao' + (destaque ? ' mkv-destaque' : '')}>
      <div className="mkv-rotulo">{c.rotulo}{c.parcial ? <span className="mkv-parcial">parcial</span> : null}</div>
      <div className="mkv-valor">{c.valor == null ? '—' : 'R$ ' + brl(c.valor) + (c.marca || '')}</div>
      <div className="mkv-frase">{c.frase}</div>
      {c.cobertura && c.cobertura.length ? <div className="mkv-cobertura">{c.cobertura.join(' · ')}</div> : null}
      {/* o rodapé diz o período (21/10/2026): a Visão geral abre em "Este mês", e ninguém pode achar que o número é do ano */}
      <div className="mkv-pe">
        {periodo && periodo.id ? <span className="mkv-periodo">período: {mkvPeriodoNome(periodo)}</span> : null}
        {c.acao && onAbrir ? <button type="button" className="fc-linkbtn mkv-abrir" onClick={() => onAbrir(c)}>{c.acao} →</button> : null}
      </div>
    </div>
  );
}

function MarketplacePainel({ onNav }) {
  const fm = useMktFiltros();
  const per = fm.periodo || {};
  const [base, setBase] = React.useState(null);     // null = lendo
  const [erro, setErro] = React.useState(null);
  const [aten, setAten] = React.useState(null);     // null = lendo
  const [rk, setRk] = React.useState(0);
  const de = per.inicio || '', ate = per.fim || '';
  // 🔴 dependências só de TEXTO estável: a chave do período e a das lojas (CLAUDE.md § 7, o laço do relógio)
  React.useEffect(() => {
    let vivo = true;
    setBase(null); setErro(null);
    if (!de || !ate || per.incompleto) return () => { vivo = false; };
    lerResultadoEstimado(de, ate, { soMarketplace: true, recebido: true }).then((r) => {
      if (!vivo) return;
      if (r.error) setErro(r.error); else setBase(r.base);
    }, (e) => { if (vivo) setErro(e); });
    return () => { vivo = false; };
  }, [per.key, rk]);   // eslint-disable-line react-hooks/exhaustive-deps
  React.useEffect(() => {
    let vivo = true;
    setAten(null);
    mktLerAtencao(fm.chaveLojas ? fm.chaveLojas.split(',') : []).then((d) => { if (vivo) setAten(d || {}); }, () => { if (vivo) setAten({ sincErro: true }); });
    return () => { vivo = false; };
  }, [fm.chaveLojas, rk]);   // eslint-disable-line react-hooks/exhaustive-deps
  const res = React.useMemo(() => (base ? mktResultadoEstimado(Object.assign({}, base, { passaLoja: fm.passaLoja })) : null), [base, fm.chaveLojas]);   // eslint-disable-line react-hooks/exhaustive-deps
  const cartoes = res ? mktVisaoCartoes(res) : null;
  const visivel = (rota) => (window.dashAtalhoVisivel ? window.dashAtalhoVisivel(rota) : true);
  const itens = aten && (res || erro) ? mktAtencaoItens(aten, res, base ? base.recebidoFech : null, { visivel }) : null;
  // o cartão abre os registros dele (a área, com o MESMO filtro) ou, o resultado, a tabela "Por loja" logo abaixo — o foco vai junto
  const abrirCartao = (c) => {
    if (c.rota) { onNav(c.rota); return; }
    const el = c.ancora ? document.getElementById(c.ancora) : null;
    if (el) { el.scrollIntoView({ block: 'start' }); el.focus({ preventScroll: true }); }
  };

  return (
    <div className={fm.dentroDoHub ? 'mkt-tela' : 'canvas'}>
      <PageHead title={mktTituloDaTela(fm, 'Marketplace · Visão geral')}
        sub="Pela data da venda: das vendas do período, o que já entrou, o que falta entrar e o resultado delas · o dinheiro pelo dia em que entrou é o Financeiro (DRE de caixa)"
        right={<Button variant="ghost" icon="refresh-cw" size="sm" title="Relê o que o app guarda" onClick={() => setRk((k) => k + 1)}>Atualizar</Button>} />
      {per.incompleto ? <EstadoDados estado="vazio" titulo="Escolha as duas datas do período" compacto /> : null}
      {erro ? <EstadoDados estado="erro" titulo="Não consegui ler as vendas, os custos ou as taxas do período" mensagem={String((erro && erro.message) || erro)} aoTentar={() => setRk((k) => k + 1)} compacto />
        : !res ? (per.incompleto ? null : <EstadoDados estado="carregando" titulo="Lendo as vendas, os custos e as taxas do período…" compacto />)
        : (
        <div className="mkv-cartoes">
          <MkvCartao c={cartoes.vendas} onAbrir={abrirCartao} periodo={per} />
          <MkvCartao c={cartoes.recebido} onAbrir={abrirCartao} periodo={per} />
          <MkvCartao c={cartoes.aReceber} onAbrir={abrirCartao} periodo={per} />
          <MkvCartao c={cartoes.resultado} destaque onAbrir={abrirCartao} periodo={per} />
        </div>)}

      <div className="mkv-grade">
        <div id="mkv-por-loja" tabIndex={-1} className="mkv-ancora"><Card title="Por loja">
          {!res ? <div className="muted" style={{ fontSize: 12.5, padding: '8px 0' }}>{erro ? '—' : 'Lendo…'}</div>
            : !res.lojas.length ? <div className="muted" style={{ fontSize: 12.5, padding: '8px 0' }}>Nenhuma venda de marketplace no período.</div>
            : (
            <div className="mkv-tabela">
              <table className="tbl" style={{ fontSize: 12.5 }}>
                <thead><tr>
                  <th scope="col">Loja</th><th scope="col" className="num right">Vendas</th><th scope="col" className="num right">Recebido</th>
                  <th scope="col" className="num right">A receber</th><th scope="col" className="num right">Custo</th>
                  <th scope="col" className="num right">Taxa</th><th scope="col" className="num right">Fatura do ML</th>
                  <th scope="col" className="num right">Resultado estimado</th>
                </tr></thead>
                <tbody>
                  {res.lojas.map((a) => (
                    <tr key={a.id}>
                      <td><b>{a.nome}</b><div className="mlv-sit-det">{a.plataforma} · {a.n} {a.ml ? (a.n === 1 ? 'venda' : 'vendas') : (a.n === 1 ? 'fechamento' : 'fechamentos')}</div></td>
                      <td className="num right">R$ {brl(a.vendas)}</td>
                      <td className="num right">{a.ml && !res.recebidoLido ? '—' : 'R$ ' + brl(a.recebido)}
                        {a.pagNaoLidos ? <div className="mlv-sit-det">{a.pagNaoLidos} sem pagamento lido</div> : null}</td>
                      <td className="num right">{a.ml && !res.recebidoLido ? '—' : 'R$ ' + brl(a.aReceber)}</td>
                      <td className="num right muted">R$ {brl(a.custo)}</td>
                      <td className="num right">R$ {brl(a.taxa)}{a.taxaEstimada > 0.004 ? <div className="mlv-sit-det">R$ {brl(a.taxaEstimada)} estimada</div> : null}</td>
                      <td className="num right">{a.ml ? 'R$ ' + brl(a.fatura) : <span className="muted">—</span>}</td>
                      <td className="num right" style={{ fontWeight: 600, color: a.resultado < 0 ? 'var(--tm-danger)' : 'var(--tm-fg-1)' }}>
                        {res.resultado.oculto ? '—' : 'R$ ' + brl(a.resultado)}{a.nFora ? <div className="mlv-sit-det">{a.nFora} fora (sem taxa)</div> : null}</td>
                    </tr>))}
                </tbody>
              </table>
            </div>)}
          <div className="mkv-nota">O resultado é estimado e não é lucro: tira das vendas o custo dos itens (o congelado no pedido), a taxa de cada venda — a real
            ou declarada onde existe, a estimada pelas 3 últimas declarações da loja onde falta — e a fatura do ML pelo mês da cobrança. Não tira folha, despesas
            fixas nem o imposto da empresa. A mesma conta mora em Relatórios › Financeiro, no Faturamento por canal e no Dashboard.</div>
        </Card></div>
        <Card title="Precisa da sua atenção">
          <MktAtencao onNav={onNav} itens={itens || []} lendo={!itens} periodoNome={per.nome ? per.nome.toLowerCase() : ''} />
        </Card>
      </div>
    </div>
  );
}

Object.assign(window, { MarketplacePainel, mktVisaoCartoes, mktAtencaoItens, mktLerAtencao, MktAtencao, mkvPeriodoNome });
