/* global React, Icon, Button, Badge, Chip, PageHead, EstadoDados, TabelaDados, PainelRegistro, brl, fmtDate, fmtDatetime, ymdLocal,
   useMktFiltros, mktTituloDaTela, mktFechFiltroServidor, mktFechAplicar, lerTodas, emLotes, mktDeclaracaoDoLancamento,
   mktEstadoRepasse, MKT_REPASSE_ESTADOS, mktLerConferencia, mktRendimentosFaltaTabela, MKT_PAGADOR_NO_EXTRATO,
   mktPagadoresDaLoja, normNome, periodoLabel, mktRotuloDeclaracao, RepasseForm, MktComContas, MKT_REC_TOLERANCIA */
// ============================================================
// Marketplace_Recebimentos.jsx — Marketplace › Recebimentos (Lote D do redesenho, 12/10/2026)
//
// O dinheiro que JÁ ESTÁ no caixa LIGADO aos fechamentos (N:N: um saque para várias semanas, várias entradas para uma
// semana), a diferença CLASSIFICADA, e o Mercado Livre com "liberação na Fase 2". As seis decisões do PM sobre o passo 0:
//  1. dinheiro novo fora do mês declarado entra pelo LÍQUIDO (o que caiu) e a diferença se classifica sem conta; o
//     "Registrar repasse" de antes fica só para completar os registros antigos (os 4 "baixado sem taxa" da Daforsil);
//  2. a diferença classificada é "Taxa de marketplace" (a série do DRE não se parte), com o TIPO na diferença e na linha;
//  3. o histórico guarda a fotografia do dinheiro; estornar nunca apaga;
//  4. UMA definição de estado: `mktEstadoRepasse` (db.js) aprende os vínculos — Lançamentos, Painel, Faturamento por canal e
//     esta tela leem a mesma;
//  5. lê quem vê o Marketplace OU o Financeiro;
//  6. dinheiro sem pagador de plataforma NÃO é oferecido — a tela conta quantos e manda acertar o pagador no Financeiro.
// O que não é estilo:
//  - quem grava é o BANCO (as funções do SUPABASE_marketplace-loteD.sql): as travas (conta da loja, pagador, Σ ≤ o que
//    entrou, Σ ≤ esperado + MKT_REC_TOLERANCIA, "já está no caixa?") valem no servidor; aqui elas só explicam antes;
//  - LIGAR e ESTORNAR não mudam número nenhum de caixa (é a prova Daforsil: PROVA_recebimentos-mkt.js e a réplica);
//    só dois caminhos mudam número, e a tela diz qual e quanto antes: registrar dinheiro NOVO (o saldo da conta, pelo
//    valor) e classificar FORA do mês declarado (a "Taxa de marketplace" do DRE de Relatórios, pelo valor);
//  - o liberado da Shopee é da LOJA e do MÊS (a declaração), nunca dividido por semana.
// ============================================================

const MKT_REC_JANELA_DIAS = 15;       // a janela do "já está no caixa?" = o "+ 15" da mkt_registrar_recebimento
const MKT_REC_TIPOS = [
  { id: 'taxa', rotulo: 'Taxa' }, { id: 'frete', rotulo: 'Frete' }, { id: 'devolucao', rotulo: 'Devolução' }, { id: 'ajuste', rotulo: 'Ajuste' },
];
const MKT_REC_SITUACOES = {
  fora_do_caixa:   { rotulo: 'fora do caixa do app', tom: 'neutral' },
  sem_dinheiro:    { rotulo: 'sem dinheiro ligado', tom: 'neutral' },
  com_diferenca:   { rotulo: 'com diferença a classificar', tom: 'warning' },
  na_declaracao:   { rotulo: 'recebido · o resto está na declaração', tom: 'success' },
  conferido:       { rotulo: 'recebido e conferido', tom: 'success' },
  acima:           { rotulo: 'acima do esperado', tom: 'warning' },
  cheio_declarado: { rotulo: 'venda cheia em mês declarado — conferir', tom: 'warning' },
  antigo_ok:       { rotulo: 'repasse registrado (o registro de antes)', tom: 'success' },
  antigo_conferir: { rotulo: 'registro de antes — conferir', tom: 'warning' },
};
const MKT_REC_FILTROS = [
  ['fila', 'Todos na fila'], ['sem_dinheiro', 'Sem dinheiro ligado'], ['com_diferenca', 'Com diferença'],
  ['na_declaracao', 'Diferença na declaração'], ['classificada', 'Classificada'], ['conferidos', 'Conferidos'], ['conferir', 'Para conferir'],
];
const MKT_REC_FECH_COLS = 'id,numero,marketplace_loja_id,periodo_inicio,periodo_fim,data_venda,status,total,origem';
const MKT_REC_DINHEIRO_COLS = 'id,referencia,cliente_nome,observacoes,valor,valor_recebido,status,data_pagamento,conta_id,natureza,pedido_id';
const MKT_REC_DECL_COLS = 'id,loja_id,periodo_inicio,periodo_fim,pagamento_liberado,taxa_gravada,taxas,cupons_rebates,subtotal_frete,frete_comprador,subtotal_mercadoria,preco_produto';
const MKT_REC_ABRIR = 'tm-mkt-rec-abrir';   // sessionStorage: o fechamento a abrir ao chegar (vindo de Vendas › Fechamentos)

// "abrir este fechamento ao chegar" (Vendas › Fechamentos manda para cá): guardado na aba; sem armazenamento, abre a lista
function mktRecAbrirDepois(id) {
  try { window.sessionStorage.setItem(MKT_REC_ABRIR, id); return true; } catch (e) { return false; }
}

// ── funções puras (a PROVA_recebimentos-mkt.js roda estas) ──────────────────────────────────────────────────────
function mktRecR2(x) { return Math.round((Number(x) || 0) * 100) / 100; }
function mktRecMaisDias(ymd, n) {
  const m = String(ymd || '').match(/^(\d{4})-(\d{2})-(\d{2})/);
  return m ? new Date(Date.UTC(+m[1], +m[2] - 1, +m[3] + n)).toISOString().slice(0, 10) : '';
}
function mktRecTexto(r) { return normNome([r && r.cliente_nome, r && r.referencia, r && r.observacoes].join(' ')); }
// a plataforma do PAGADOR escrito no título (a MESMA lista da trava de 22/09 e do SQL): 'shopee' | 'tiktok' | 'mercado livre' | ''
function mktRecPagador(r) {
  const t = mktRecTexto(r);
  return Object.keys(MKT_PAGADOR_NO_EXTRATO).find((k) => MKT_PAGADOR_NO_EXTRATO[k].some((p) => t.includes(p))) || '';
}
function mktRecCasaLoja(loja, r) {
  const t = mktRecTexto(r);
  return mktPagadoresDaLoja(loja).some((p) => t.includes(p));
}
// o DINHEIRO no caixa: título sem pedido, que entrou, com data, receita (venda ou sem natureza) — a regra da mkt__vincular
function mktRecEhDinheiro(r) {
  return !!r && !r.pedido_id && !!r.data_pagamento && window.finEntrouNoCaixa(r) && window.naturezaEhReceita(r.natureza);
}
// cada dinheiro: o que entrou (finValorQueEntrou: o parcial vale o recebido), o já ligado (vínculos ATIVOS) e o livre
function mktRecDinheiros(recs, vinculos) {
  const lig = {}, porDinheiro = {};
  (Array.isArray(vinculos) ? vinculos : []).forEach((v) => {
    if (!v || !v.conta_receber_id) return;
    (porDinheiro[v.conta_receber_id] = porDinheiro[v.conta_receber_id] || []).push(v);
    if (!v.estornado_em) lig[v.conta_receber_id] = (lig[v.conta_receber_id] || 0) + (Number(v.valor) || 0);
  });
  return (Array.isArray(recs) ? recs : []).filter(mktRecEhDinheiro).map((r) => {
    const entrou = mktRecR2(window.finValorQueEntrou(r)), ligado = mktRecR2(lig[r.id] || 0);
    return { id: r.id, r, entrou, ligado, livre: mktRecR2(entrou - ligado), pagador: mktRecPagador(r),
      data: String(r.data_pagamento).slice(0, 10), contaId: r.conta_id || null, vinculos: porDinheiro[r.id] || [] };
  });
}
// a referência do título traz o período DESTE fechamento ("06–12/07")? É a sugestão — nunca o vínculo automático
function mktRecRefCasa(r, p) {
  if (!r || !p || !p.periodo_inicio || !p.periodo_fim) return false;
  const m = String([r.referencia, r.observacoes].join(' ')).match(/(\d{2})(?:\/(\d{2}))?\s*[-\u2013]\s*(\d{2})\/(\d{2})/);
  if (!m) return false;
  const dm = (iso) => String(iso).slice(8, 10) + '/' + String(iso).slice(5, 7);
  return dm(p.periodo_inicio) === m[1] + '/' + (m[2] || m[4]) && dm(p.periodo_fim) === m[3] + '/' + m[4];
}
// os dinheiros que se ligam a este fechamento: da CONTA de recebimento da loja, do PAGADOR da plataforma, com saldo livre,
// a partir do início do período. A sugestão pela referência vem primeiro; depois os da janela (fim + 15 dias).
function mktRecCandidatos(p, loja, dinheiros) {
  if (!p || !loja || !loja.conta_id) return [];
  const de = String(p.periodo_inicio || p.data_venda || '').slice(0, 10);
  const ate = mktRecMaisDias(p.periodo_fim || p.data_venda, MKT_REC_JANELA_DIAS);
  return (Array.isArray(dinheiros) ? dinheiros : [])
    .filter((d) => d.contaId === loja.conta_id && d.livre > 0.004 && d.data >= de && mktRecCasaLoja(loja, d.r))
    .map((d) => ({ ...d, naJanela: d.data <= ate, sugestao: mktRecRefCasa(d.r, p) }))
    .sort((a, b) => (Number(b.sugestao) - Number(a.sugestao)) || (Number(b.naJanela) - Number(a.naJanela)) || a.data.localeCompare(b.data));
}
// dinheiro da MESMA plataforma, livre, na janela, numa conta que não é a de NENHUMA loja dela (a TopMix TikTok recebendo
// Shopee, ou sem conta): não se liga (o banco recusa) — a tela conta e diz. A conta de outra loja da plataforma é dela.
function mktRecEmOutrasContas(p, loja, dinheiros, lojas) {
  if (!p || !loja) return { n: 0, soma: 0 };
  const de = String(p.periodo_inicio || p.data_venda || '').slice(0, 10), ate = mktRecMaisDias(p.periodo_fim || p.data_venda, MKT_REC_JANELA_DIAS);
  const daPlataforma = new Set((Array.isArray(lojas) ? lojas : []).filter((l) => l.plataforma === loja.plataforma && l.conta_id).map((l) => l.conta_id));
  const l = (Array.isArray(dinheiros) ? dinheiros : []).filter((d) => d.contaId !== loja.conta_id && !daPlataforma.has(d.contaId) && d.livre > 0.004
    && d.data >= de && d.data <= ate && mktRecCasaLoja(loja, d.r));
  return { n: l.length, soma: mktRecR2(l.reduce((a, d) => a + d.livre, 0)) };
}
// O RESUMO DE CADA FECHAMENTO: esperado (P), recebido na conta (R), diferenças (D), saldo (S = P − R − D) e a situação.
//  · modelo novo: R = Σ vínculos ativos; D = as linhas de taxa amarradas (as classificadas fora do mês declarado; o ajuste
//    negativo entra com sinal) + as diferenças que só IDENTIFICAM (no mês que tem declaração AGORA);
//  · registro de antes (título próprio): R = o que entrou nele − a taxa paga NA CONTA (o repasse antigo grava o bruto como
//    recebido e paga a taxa na mesma conta: o dinheiro de verdade é a diferença); D = a taxa amarrada;
//  · "fora do caixa do app": o fim do período + 15 dias cai antes da âncora da conta da loja — o dinheiro dele é anterior
//    ao caixa do app (e não há o que ligar).
function mktRecResumo({ fechamentos, lojas, contas, recsLegado, taxas, decls, vinculos, diferencas }) {
  const por = (lista, chave) => { const o = {}; (Array.isArray(lista) ? lista : []).forEach((x) => { if (x && x[chave]) (o[x[chave]] = o[x[chave]] || []).push(x); }); return o; };
  const lojaPor = {}; (lojas || []).forEach((l) => { lojaPor[l.id] = l; });
  const contaPor = {}; (contas || []).forEach((c) => { contaPor[c.id] = c; });
  const recP = por(recsLegado, 'pedido_id'), txP = por(taxas, 'pedido_id'), vP = por(vinculos, 'pedido_id'), dP = por(diferencas, 'pedido_id');
  const soma = (l) => mktRecR2((l || []).reduce((a, x) => a + (Number(x.valor) || 0), 0));
  return (Array.isArray(fechamentos) ? fechamentos : []).map((p) => {
    const loja = lojaPor[p.marketplace_loja_id] || null;
    const conta = loja && loja.conta_id ? contaPor[loja.conta_id] || null : null;
    const P = mktRecR2(p.total);
    const legados = recP[p.id] || [], tx = txP[p.id] || [], vs = vP[p.id] || [], ds = dP[p.id] || [];
    const vAt = vs.filter((v) => !v.estornado_em), dAt = ds.filter((d) => !d.estornado_em);
    const decl = mktDeclaracaoDoLancamento(p, decls);
    const temNovo = vAt.length > 0 || dAt.length > 0;
    const V = soma(vAt), L = soma(tx);
    const Ds = decl ? soma(dAt.filter((d) => !d.conta_pagar_id)) : 0;
    let R, D;
    if (legados.length) {
      const entrouLeg = legados.reduce((a, r) => a + (window.finEntrouNoCaixa(r) ? window.finValorQueEntrou(r) : 0), 0);
      const taxaNaConta = tx.filter((t) => t.status === 'Pago' && t.conta_id && t.data_pagamento).reduce((a, t) => a + (Number(t.valor) || 0), 0);
      R = mktRecR2(entrouLeg - taxaNaConta); D = L;
    } else { R = V; D = mktRecR2(L + Ds); }
    const S = mktRecR2(P - R - D);
    const estado = mktEstadoRepasse(legados, tx, !!decl, temNovo ? { temNovo, previsto: P, vinculado: V } : null);
    const ancora = conta && conta.data_saldo_inicial ? String(conta.data_saldo_inicial).slice(0, 10) : '';
    const fimMais = mktRecMaisDias(p.periodo_fim || p.data_venda, MKT_REC_JANELA_DIAS);
    const foraDoCaixa = !!ancora && !!fimMais && fimMais < ancora && !legados.length && !temNovo;
    let sit;
    if (foraDoCaixa) sit = 'fora_do_caixa';
    else if (legados.length) sit = (estado === 'registrado' || estado === 'taxa_na_declaracao') ? 'antigo_ok' : 'antigo_conferir';
    else if (R <= 0.004) sit = 'sem_dinheiro';
    else if (decl && R >= P - MKT_REC_TOLERANCIA) sit = 'cheio_declarado';   // o bruto a centavos: a Shopee sempre retém
    else if (Math.abs(S) < 0.005) sit = 'conferido';
    else if (S < 0) sit = 'acima';
    else sit = decl ? 'na_declaracao' : 'com_diferenca';
    return { id: p.id, p, loja, conta, P, R, D, S, V, L, Ds, decl, estado, sit, temNovo, foraDoCaixa, ancora, legados, tx,
      vinculos: vs, vAtivos: vAt, diferencas: ds, dAtivas: dAt, classificada: dAt.length > 0 };
  });
}
// o filtro rápido da fila (a fila = tudo que não está fora do caixa do app)
function mktRecPassaFiltro(x, filtro) {
  if (x.sit === 'fora_do_caixa') return false;
  switch (filtro) {
    case 'sem_dinheiro': return x.sit === 'sem_dinheiro';
    case 'com_diferenca': return x.sit === 'com_diferenca';
    case 'na_declaracao': return x.sit === 'na_declaracao';
    case 'classificada': return x.classificada;
    case 'conferidos': return x.sit === 'conferido' || x.sit === 'antigo_ok';
    case 'conferir': return x.sit === 'acima' || x.sit === 'cheio_declarado' || x.sit === 'antigo_conferir';
    default: return true;
  }
}
// quanto ainda cabe NESTE fechamento (esperado + tolerância − ligado − diferenças positivas): a mesma conta do banco
function mktRecCabe(x) {
  const dPos = mktRecR2((x.dAtivas || []).filter((d) => Number(d.valor) > 0).reduce((a, d) => a + Number(d.valor), 0));
  return Math.max(0, mktRecR2(x.P + MKT_REC_TOLERANCIA - x.V - dPos));
}
// a pendência da decisão 6: dinheiro de venda no caixa SEM pagador de plataforma no texto — não é oferecido aqui
function mktRecSemPagador(recs) {
  const l = (Array.isArray(recs) ? recs : []).filter(mktRecEhDinheiro).filter((r) => !mktRecPagador(r));
  return { n: l.length, soma: mktRecR2(l.reduce((a, r) => a + window.finValorQueEntrou(r), 0)) };
}
// esperado, recebido e liberado POR LOJA E MÊS (o liberado é da declaração do mês inteiro — nunca dividido por semana)
function mktRecPorLojaMes(linhas, decls) {
  const g = {};
  (Array.isArray(linhas) ? linhas : []).filter((x) => x.sit !== 'fora_do_caixa' && x.loja).forEach((x) => {
    const mes = String(x.p.data_venda || '').slice(0, 7), k = x.loja.id + '|' + mes;
    const o = g[k] || (g[k] = { loja: x.loja, mes, esperado: 0, recebido: 0, n: 0, liberado: null });
    o.esperado = mktRecR2(o.esperado + x.P); o.recebido = mktRecR2(o.recebido + x.R); o.n++;
  });
  Object.values(g).forEach((o) => {
    const fimMes = mktRecMaisDias(mktRecMaisDias(o.mes + '-01', 32).slice(0, 8) + '01', -1);
    const d = (Array.isArray(decls) ? decls : []).find((r) => r.loja_id === o.loja.id && String(r.periodo_inicio).slice(0, 10) <= o.mes + '-01'
      && String(r.periodo_fim).slice(0, 10) >= fimMes);
    o.liberado = d ? mktRecR2(d.pagamento_liberado) : null;
  });
  return Object.values(g).sort((a, b) => (a.loja.nome || '').localeCompare(b.loja.nome || '') || a.mes.localeCompare(b.mes));
}

// ── as gravações (cada uma, uma função do banco numa transação) ──────────────────────────────────────────────────
async function mktRecRpc(nome, args, op) {
  const neg = window.tmNegadoAqui ? window.tmNegadoAqui(op || 'editar') : null;
  if (neg) return { error: { message: neg } };
  const { data, error } = await window.tmSupabase.rpc(nome, args);
  return error ? { error } : { data };
}
const mktRecLigar = (dinheiro, alocacoes) => mktRecRpc('mkt_vincular_recebimento', { p_dinheiro: dinheiro, p_alocacoes: alocacoes });
const mktRecEstornarVinculo = (id, motivo) => mktRecRpc('mkt_estornar_vinculo', { p_vinculo: id, p_motivo: motivo });
const mktRecClassificar = (pedido, itens) => mktRecRpc('mkt_classificar_diferenca', { p_pedido: pedido, p_itens: itens });
const mktRecEstornarDiferenca = (id, motivo) => mktRecRpc('mkt_estornar_diferenca', { p_diferenca: id, p_motivo: motivo });
const mktRecRegistrar = (a) => mktRecRpc('mkt_registrar_recebimento', { p_conta: a.conta, p_data: a.data, p_valor: a.valor, p_alocacoes: a.alocacoes,
  p_confirmo_outro: !!a.confirmo, p_observacao: a.observacao || null }, ['criar', 'editar']);
function mktRecErro(e) { return (e && (e.message || e.code)) || 'erro desconhecido'; }

// ── a leitura (as duas vistas) ──────────────────────────────────────────────────────────────────────────────────
// `f` = o filtro do servidor (mktFechFiltroServidor); a lista de dependências é a CHAVE dele (texto estável).
function useMktRecDados(f, reloadKey, comML) {
  const [d, setD] = React.useState({ lendo: true, erro: '' });
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      if (!window.tmSupabase) { setD({ lendo: false, erro: '', fech: [], lojas: [], contas: [], legados: [], taxas: [], decls: [], conf: { vinculos: [], diferencas: [] }, recs: [], ml: [] }); return; }
      const sb = window.tmSupabase;
      setD((x) => ({ ...x, lendo: true }));
      const [fech, lojas, contas, decls, recs, conf, ml] = await Promise.all([
        f.nada ? Promise.resolve({ data: [], error: null })
          : lerTodas(() => mktFechAplicar(sb.from('pedidos').select(MKT_REC_FECH_COLS).eq('tipo_registro', 'fechamento').neq('status', 'Cancelado'), f)),
        sb.from('marketplace_lojas').select('id,nome,plataforma,empresa_id,conta_id,situacao'),
        sb.from('contas_bancarias').select('id,nome,data_saldo_inicial,situacao'),
        sb.from('marketplace_rendimentos').select(MKT_REC_DECL_COLS),
        lerTodas(() => sb.from('contas_receber').select(MKT_REC_DINHEIRO_COLS).is('pedido_id', null)),
        mktLerConferencia(null),
        // Mercado Livre: o previsto (o total dos pedidos que vieram da sincronização, no período) — a liberação é da Fase 2
        f.nada || !comML ? Promise.resolve({ data: [], error: null })
          : lerTodas(() => mktFechAplicar(sb.from('pedidos').select('id,total,marketplace_loja_id').eq('origem', 'mercadolivre').eq('status', 'Atendido'), f)),
      ]);
      const declErr = decls.error && !mktRendimentosFaltaTabela(decls.error) ? decls.error : null;
      const erro1 = fech.error || lojas.error || contas.error || declErr || recs.error || conf.error || ml.error;
      if (erro1) { if (vivo) setD({ lendo: false, erro: mktRecErro(erro1) }); return; }
      const ids = (fech.data || []).map((p) => p.id);
      const [leg, tx] = await Promise.all([
        ids.length ? emLotes(ids, (l) => sb.from('contas_receber').select('id,pedido_id,valor,valor_recebido,status,data_pagamento,conta_id,referencia').in('pedido_id', l)) : Promise.resolve({ data: [] }),
        ids.length ? emLotes(ids, (l) => sb.from('contas_pagar').select('id,pedido_id,valor,status,conta_id,data_pagamento,referencia').eq('categoria', 'Taxa de marketplace').in('pedido_id', l)) : Promise.resolve({ data: [] }),
      ]);
      const erro2 = leg.error || tx.error;
      if (!vivo) return;
      if (erro2) { setD({ lendo: false, erro: mktRecErro(erro2) }); return; }
      setD({ lendo: false, erro: '', lidoEm: new Date().toISOString(), fech: fech.data || [], lojas: lojas.data || [], contas: contas.data || [],
        legados: leg.data || [], taxas: tx.data || [], decls: decls.error ? [] : (decls.data || []), recs: recs.data || [],
        conf: { vinculos: conf.vinculos, diferencas: conf.diferencas, instalado: conf.instalado !== false }, ml: ml.data || [] });
    })();
    return () => { vivo = false; };
  }, [f.chave, reloadKey, comML]);   // eslint-disable-line react-hooks/exhaustive-deps
  return d;
}

// ── o painel de UM FECHAMENTO: os valores, o dinheiro ligado, as diferenças, e as duas ações dentro dele ──────────
function MktRecEstornar({ rotulo, onConfirmar, salvando }) {
  const [aberto, setAberto] = React.useState(false);
  const [motivo, setMotivo] = React.useState('');
  if (!aberto) return <button type="button" className="fc-linkbtn" {...window.tmPedeProps('editar', { onClick: () => setAberto(true) })}>Estornar</button>;
  return (
    <div className="mrc-estorno">
      <input className="input" type="text" placeholder={'Motivo do estorno de ' + rotulo} aria-label="Motivo do estorno" value={motivo} onChange={(e) => setMotivo(e.target.value)} />
      <Button size="sm" variant="secondary" onClick={() => { setAberto(false); setMotivo(''); }}>Cancelar</Button>
      <Button size="sm" variant="danger-ghost" pede="editar" disabled={salvando || motivo.trim().length < 3} onClick={() => onConfirmar(motivo.trim())}>Estornar</Button>
    </div>
  );
}

function PainelRecFechamento({ x, dinheiros, lojas, contaPor, onFechar, onGravou, onNav }) {
  const { p, loja, conta } = x;
  const [modo, setModo] = React.useState(null);           // null | 'ligar' | 'novo' | 'classificar'
  const [msg, setMsg] = React.useState('');
  const [salvando, setSalvando] = React.useState(false);
  const [histo, setHisto] = React.useState(false);
  const [repassando, setRepassando] = React.useState(false);
  const info = MKT_REC_SITUACOES[x.sit] || {};
  const per = (p.periodo_inicio && p.periodo_fim) ? periodoLabel(p.periodo_inicio, p.periodo_fim) : (fmtDate(p.data_venda) || '—');
  const contaNome = (id) => (contaPor[id] ? contaPor[id].nome : 'outra conta');
  const legado = x.legados.length > 0;
  const fim = async (r, feito) => {
    setSalvando(false);
    if (r && r.error) { setMsg('Nada foi gravado: ' + mktRecErro(r.error)); return false; }
    setMsg(feito); setModo(null); onGravou();
    return true;
  };
  // parte gravou e parte não (vários dinheiros, uma transação por dinheiro): diz o que ficou, e relê
  const parcial = (texto) => { setSalvando(false); setMsg(texto); setModo(null); onGravou(); };
  const estornarV = async (v, motivo) => { setSalvando(true); await fim(await mktRecEstornarVinculo(v.id, motivo), `Vínculo de R$ ${brl(v.valor)} estornado: o dinheiro continua no caixa, livre para outro fechamento.`); };
  const estornarD = async (d, motivo) => { setSalvando(true); await fim(await mktRecEstornarDiferenca(d.id, motivo), `Diferença de R$ ${brl(d.valor)} estornada${d.conta_pagar_id ? ': a linha dela saiu de Contas a Pagar' : ''}.`); };
  const acoes = [];
  if (legado && x.sit === 'antigo_conferir') {
    acoes.push({ id: 'repasse', rotulo: 'Conferir repasse (o registro de antes)', icone: 'landmark', pede: ['editar', 'criar'], onClick: () => setRepassando(true) });
  } else if (!legado && x.sit !== 'fora_do_caixa') {
    acoes.push({ id: 'classificar', rotulo: 'Conferir diferença', icone: 'list-checks', variante: 'secondary', pede: 'editar', desabilitado: salvando,
      onClick: () => { setModo('classificar'); setMsg(''); } });
    acoes.push({ id: 'ligar', rotulo: 'Registrar recebimento', icone: 'link', pede: 'editar', desabilitado: salvando,
      onClick: () => { setModo('ligar'); setMsg(''); } });
  }
  const linhaD = (d) => (MKT_REC_TIPOS.find((t) => t.id === d.tipo) || {}).rotulo || d.tipo;
  return (
    <PainelRegistro aberto={true} onFechar={onFechar} eyebrow="Recebimento do fechamento" titulo={'Fechamento ' + (p.numero || '')}
      subtitulo={[loja ? loja.nome + ' · ' + loja.plataforma : '', per].filter(Boolean).join(' · ')} acoes={acoes} largura={720}>
      {msg && <div className="card mla-msg" role="status">{msg}</div>}
      <section className="pr-secao" aria-label="Os valores">
        <div className="pr-secao-titulo">Esperado, recebido e o que falta</div>
        <div className="mrc-valores">
          <div><div className="pr-rot">Esperado (venda cheia)</div><b>R$ {brl(x.P)}</b></div>
          <div><div className="pr-rot">Recebido na conta</div><b>R$ {brl(x.R)}</b></div>
          <div><div className="pr-rot">Diferenças classificadas</div><b>R$ {brl(x.D)}</b></div>
          <div><div className="pr-rot">Saldo</div><b className={Math.abs(x.S) < 0.005 ? '' : 'mrc-aberto'}>R$ {brl(x.S)}</b></div>
        </div>
        <div className="mlv-sit"><Badge tone={info.tom}>{x.sit === 'fora_do_caixa' ? (x.decl ? 'declarado · fora do caixa do app' : 'fora do caixa do app · sem declaração') : info.rotulo}</Badge></div>
        <div className="pr-nota">
          {x.sit === 'fora_do_caixa' && <>O período termina antes de a conta {conta ? conta.nome : 'da loja'} começar no app ({fmtDate(x.ancora)}): o dinheiro dele é anterior ao caixa do app. Não há o que ligar{x.decl ? ', e a retenção está na declaração ' + mktRotuloDeclaracao(x.decl) : ''}.</>}
          {x.sit === 'sem_dinheiro' && <>Nenhum dinheiro ligado ainda. "Registrar recebimento" mostra os saques da {loja ? loja.plataforma : 'plataforma'} na conta da loja que ainda estão livres — e só registra dinheiro novo se ele não estiver lá.</>}
          {x.sit === 'com_diferenca' && <>O que entrou é menor que a venda cheia: a diferença (R$ {brl(x.S)}) é o que a plataforma reteve, a classificar — taxa, frete, devolução ou ajuste. O app não chama tudo de taxa sozinho. Ou ainda falta dinheiro: ligue antes de classificar.</>}
          {x.sit === 'na_declaracao' && <>Mês com declaração ({mktRotuloDeclaracao(x.decl)}): a retenção do mês já está na linha da declaração. Classificar aqui só IDENTIFICA o que foi (nada é lançado).</>}
          {x.sit === 'conferido' && <>Recebido e conferido ao centavo: o dinheiro ligado e as diferenças classificadas fecham a venda cheia.</>}
          {x.sit === 'acima' && <>Ligado e classificado passam do esperado em R$ {brl(-x.S)}. Até R$ {brl(MKT_REC_TOLERANCIA)} é arredondamento da plataforma: classifique um ajuste negativo. Mais que isso, estorne o que não é deste fechamento.</>}
          {x.sit === 'cheio_declarado' && <>O dinheiro ligado é a venda cheia, num mês com declaração — e nas declarações a Shopee sempre reteve parte. Confira no extrato do banco o que entrou: se o título traz o bruto, o saldo da conta está acima do banco pela retenção.</>}
          {x.sit === 'antigo_ok' && <>Este fechamento foi conferido pelo registro de antes (o título próprio e a taxa amarrada). Ele fica como está.</>}
          {x.sit === 'antigo_conferir' && <>Registro de antes, para conferir ({(MKT_REPASSE_ESTADOS[x.estado] || {}).rotulo || x.estado}): ele se completa pelo "Conferir repasse", com o extrato do banco na mão — completar mexe no saldo da conta, de propósito.</>}
        </div>
        {loja && loja.plataforma === 'Shopee' && <div className="mlv-sit-det">O liberado pela Shopee é da loja e do mês (a declaração), não deste fechamento — está em "Por loja e mês", na lista.</div>}
      </section>

      {modo === 'ligar' && <MktRecLigar x={x} dinheiros={dinheiros} lojas={lojas} contaNome={contaNome} salvando={salvando} setSalvando={setSalvando} fim={fim} parcial={parcial}
        onNovo={() => setModo('novo')} onCancelar={() => setModo(null)} />}
      {modo === 'novo' && <MktRecNovo x={x} dinheiros={dinheiros} contaNome={contaNome} salvando={salvando} setSalvando={setSalvando} fim={fim}
        onVoltar={() => setModo('ligar')} />}
      {modo === 'classificar' && <MktRecClassificar x={x} salvando={salvando} setSalvando={setSalvando} fim={fim} onCancelar={() => setModo(null)} />}

      <section className="pr-secao" aria-label="Dinheiro ligado">
        <div className="pr-secao-titulo">Dinheiro ligado</div>
        {legado ? x.legados.map((r) => (
          <div key={r.id} className="mrc-item"><span>{fmtDate(r.data_pagamento) || '—'} · {contaNome(r.conta_id)}</span>
            <span>título próprio (registro de antes): R$ {brl(r.valor)} · {r.status}{Number(r.valor_recebido) > 0 ? ` · entrou R$ ${brl(r.valor_recebido)}` : ''}</span></div>))
          : x.vAtivos.length ? x.vAtivos.map((v) => {
            const d = (dinheiros || []).find((z) => z.id === v.conta_receber_id);
            const outros = d ? mktRecR2(d.ligado - v.valor) : null;
            return (
              <div key={v.id} className="mrc-item">
                <span>{fmtDate(v.dinheiro_data)} · {contaNome(v.dinheiro_conta_id)}</span>
                <span><b>R$ {brl(v.valor)}</b> deste dinheiro (entrou R$ {brl(v.dinheiro_valor)}{outros > 0.004 ? `; R$ ${brl(outros)} em outros fechamentos` : ''}){v.origem === 'registro' ? ' · registrado aqui' : ''}
                  <div className="mlv-sit-det">{v.dinheiro_referencia || ''} · ligado em {fmtDatetime(v.criado_em)}</div></span>
                <MktRecEstornar rotulo={'R$ ' + brl(v.valor)} salvando={salvando} onConfirmar={(m) => estornarV(v, m)} />
              </div>);
          }) : <span className="muted">nenhum</span>}
      </section>
      <section className="pr-secao" aria-label="Diferenças classificadas">
        <div className="pr-secao-titulo">Diferenças classificadas</div>
        {legado ? (x.tx.length ? x.tx.map((t) => <div key={t.id} className="mrc-item"><span>Taxa de marketplace (registro de antes)</span><span>R$ {brl(t.valor)} · {t.status}</span></div>)
          : <span className="muted">nenhuma taxa registrada</span>)
          : x.dAtivas.length ? x.dAtivas.map((d) => (
            <div key={d.id} className="mrc-item">
              <span>{linhaD(d)}</span>
              <span><b>R$ {brl(d.valor)}</b> · {d.conta_pagar_id ? 'linha "Taxa de marketplace" em Contas a Pagar (sem conta, sem data: não mexe em saldo)' : 'só identifica (mês declarado: a retenção está na declaração)'}
                {d.observacao ? <div className="mlv-sit-det">{d.observacao}</div> : null}</span>
              <MktRecEstornar rotulo={'R$ ' + brl(d.valor)} salvando={salvando} onConfirmar={(m) => estornarD(d, m)} />
            </div>)) : <span className="muted">nenhuma</span>}
      </section>
      {(x.vinculos.length > x.vAtivos.length || x.diferencas.length > x.dAtivas.length) && (
        <section className="pr-secao" aria-label="Histórico">
          <button type="button" className="fc-linkbtn" aria-expanded={histo} onClick={() => setHisto((h) => !h)}>{histo ? 'Esconder' : 'Mostrar'} o histórico (estornados)</button>
          {histo && x.vinculos.filter((v) => v.estornado_em).concat(x.diferencas.filter((d) => d.estornado_em)).map((h) => (
            <div key={h.id} className="mrc-item mrc-estornado">
              <span>{h.tipo ? linhaD(h) : 'Dinheiro de ' + fmtDate(h.dinheiro_data)}</span>
              <span>R$ {brl(h.valor)} · estornado em {fmtDatetime(h.estornado_em)} — {h.estorno_motivo}</span>
            </div>))}
        </section>
      )}
      {repassando && (
        <MktComContas>{(contas) => (
          <RepasseForm pedido={{ ...p, num: p.numero }} loja={loja} contas={contas} onNav={onNav}
            onClose={() => setRepassando(false)} onSaved={() => { setRepassando(false); onGravou(); }} />)}
        </MktComContas>
      )}
    </PainelRegistro>
  );
}

// "Registrar recebimento", passo 1: o dinheiro JÁ ESTÁ no caixa? Os saques livres da plataforma na conta da loja.
function MktRecLigar({ x, dinheiros, lojas, contaNome, salvando, setSalvando, fim, parcial, onNovo, onCancelar }) {
  const cands = mktRecCandidatos(x.p, x.loja, dinheiros);
  const outras = mktRecEmOutrasContas(x.p, x.loja, dinheiros, lojas);
  const cabe = mktRecCabe(x);
  const [sel, setSel] = React.useState({});      // { [dinheiro]: valor (texto do campo) }
  const marcar = (d) => setSel((s) => {
    const n = { ...s };
    if (n[d.id] !== undefined) delete n[d.id];
    else {
      // a SUGESTÃO é o saldo do fechamento (o que falta para a venda cheia); a tolerância é o teto, nunca a sugestão
      const usado = Object.values(n).reduce((a, v) => a + (Number(v) || 0), 0);
      n[d.id] = String(mktRecR2(Math.max(0, Math.min(d.livre, x.S - usado))));
    }
    return n;
  });
  const total = mktRecR2(Object.values(sel).reduce((a, v) => a + (Number(v) || 0), 0));
  const invalido = Object.entries(sel).some(([id, v]) => { const d = cands.find((c) => c.id === id); return !(Number(v) > 0) || !d || Number(v) > d.livre + 0.004; });
  const ligar = async () => {
    setSalvando(true);
    const feitos = [];
    for (const [id, v] of Object.entries(sel)) {
      const r = await mktRecLigar(id, [{ pedido_id: x.id, valor: mktRecR2(v) }]);
      if (r.error) {
        // cada dinheiro é uma transação: o que já ligou FICA. Parar no primeiro que falha e dizer o feito e o não feito.
        if (!feitos.length) { await fim(r); return; }
        parcial(`Ligados ${feitos.length} de ${Object.keys(sel).length} dinheiros (R$ ${brl(feitos.reduce((a, b) => a + b, 0))}); o seguinte foi recusado: ${mktRecErro(r.error)}. Os já ligados ficam — tentar de novo liga só o que falta.`);
        return;
      }
      feitos.push(mktRecR2(v));
    }
    await fim({}, `Ligado R$ ${brl(total)} a este fechamento. Nada mudou no caixa: o dinheiro já estava lá.`);
  };
  return (
    <section className="pr-secao mrc-modo" aria-label="Registrar recebimento">
      <div className="pr-secao-titulo">Registrar recebimento · o dinheiro já está no caixa?</div>
      <div className="mlv-sit-det">Saques da {x.loja ? x.loja.plataforma : 'plataforma'} em {x.loja && x.loja.conta_id ? contaNome(x.loja.conta_id) : 'a conta da loja'}, ainda livres, desde {fmtDate(x.p.periodo_inicio || x.p.data_venda)}. Falta R$ {brl(Math.max(0, x.S))} para a venda cheia (o banco aceita até R$ {brl(cabe)}: a tolerância de R$ {brl(MKT_REC_TOLERANCIA)} é para o centavo que a plataforma arredonda).</div>
      {!x.loja || !x.loja.conta_id ? <EstadoDados estado="vazio" titulo="A loja não tem conta de recebimento" mensagem="Defina a conta em Lojas e integrações: o dinheiro só se liga ao fechamento da loja cuja conta o recebeu." compacto />
        : cands.length ? cands.map((d) => (
          <label key={d.id} className="mrc-cand">
            <input type="checkbox" checked={sel[d.id] !== undefined} onChange={() => marcar(d)} />
            <span className="mrc-cand-txt">
              <b>{fmtDate(d.data)}</b> · entrou R$ {brl(d.entrou)}{d.ligado > 0.004 ? ` · livre R$ ${brl(d.livre)}` : ''}
              {d.sugestao && <Badge tone="info">a referência cita este período</Badge>}
              {!d.naJanela && <span className="mlv-sit-det"> · fora da janela (mais de {MKT_REC_JANELA_DIAS} dias depois do fim)</span>}
              <span className="mlv-sit-det"> {d.r.referencia || d.r.cliente_nome || ''}</span>
            </span>
            {sel[d.id] !== undefined && (
              <input className="input mrc-num" type="number" min="0.01" step="0.01" aria-label={'Valor deste dinheiro para o fechamento ' + x.p.numero}
                value={sel[d.id]} onChange={(e) => setSel((s) => ({ ...s, [d.id]: e.target.value }))} />)}
          </label>))
        : <EstadoDados estado="vazio" titulo="Nenhum saque livre desta plataforma na conta da loja" mensagem="Se o dinheiro entrou e não está no app, registre-o como dinheiro novo." compacto />}
      {outras.n > 0 && <div className="mlv-sit-det">{outras.n} saque(s) da {x.loja.plataforma} na janela em conta que não é de loja {x.loja.plataforma} nenhuma, ou sem conta (R$ {brl(outras.soma)}): não se ligam a esta loja — a conta de recebimento dela é {contaNome(x.loja.conta_id)}. Se forem dela, acerte a conta do título no Financeiro.</div>}
      <div className="mrc-botoes">
        <Button size="sm" variant="ghost" onClick={onCancelar}>Cancelar</Button>
        <Button size="sm" variant="secondary" icon="plus" pede={['criar', 'editar']} disabled={salvando || !x.loja || !x.loja.conta_id} onClick={onNovo}>Não está em nenhum destes: dinheiro novo</Button>
        <Button size="sm" variant="primary" icon="link" pede="editar" disabled={salvando || !Object.keys(sel).length || invalido || total > cabe + 0.004} onClick={ligar}>
          {salvando ? 'Ligando…' : `Ligar R$ ${brl(total)}`}</Button>
      </div>
      {total > cabe + 0.004 && <div className="mlv-sit-det mrc-aviso">Passa do que cabe neste fechamento (R$ {brl(cabe)}).</div>}
    </section>
  );
}

// "Registrar recebimento", passo 2: dinheiro NOVO (que não está no app). Muda o caixa pelo valor — e a tela diz isso antes.
function MktRecNovo({ x, dinheiros, contaNome, salvando, setSalvando, fim, onVoltar }) {
  const hoje = ymdLocal(new Date());
  const [data, setData] = React.useState(hoje);
  const [valor, setValor] = React.useState('');
  const [aqui, setAqui] = React.useState('');
  const [obs, setObs] = React.useState('');
  const [confirmo, setConfirmo] = React.useState(false);
  const cands = mktRecCandidatos(x.p, x.loja, dinheiros).filter((d) => d.naJanela);
  const cabe = mktRecCabe(x);
  const v = mktRecR2(valor), a = aqui === '' ? Math.min(v, Math.max(0, x.S)) : mktRecR2(aqui);
  const pode = v > 0 && a > 0 && a <= v + 0.004 && a <= cabe + 0.004 && !!data && data <= hoje && (!cands.length || confirmo) && !salvando;
  const gravar = async () => {
    setSalvando(true);
    const r = await mktRecRegistrar({ conta: x.loja.conta_id, data, valor: v, alocacoes: [{ pedido_id: x.id, valor: mktRecR2(a) }], confirmo, observacao: obs });
    await fim(r, `Registrado R$ ${brl(v)} recebido em ${fmtDate(data)} na ${contaNome(x.loja.conta_id)}, R$ ${brl(a)} ligados a este fechamento${v - a > 0.004 ? ` (R$ ${brl(v - a)} ficam livres, em Por dinheiro)` : ''}.`);
  };
  return (
    <section className="pr-secao mrc-modo" aria-label="Dinheiro novo">
      <div className="pr-secao-titulo">Registrar recebimento · dinheiro novo</div>
      <div className="pr-nota">Isto põe R$ {brl(v)} no saldo de {contaNome(x.loja.conta_id)} em {fmtDate(data) || '—'} e nos Recebimentos do DRE de caixa: é dinheiro que entrou no banco e ainda não estava no app. Se ele já está no app, volte e ligue — registrar de novo contaria duas vezes.</div>
      {cands.length > 0 && (
        <div className="pr-nota mrc-aviso">
          Já existe R$ {brl(cands.reduce((s, d) => s + d.livre, 0))} livre da {x.loja.plataforma} em {contaNome(x.loja.conta_id)} na janela deste período ({cands.length} saque(s)). Se este dinheiro é um deles, não registre: volte e ligue.
          <label className="mrc-confirma"><input type="checkbox" checked={confirmo} onChange={(e) => setConfirmo(e.target.checked)} /> É outro dinheiro — confirmo</label>
        </div>)}
      <div className="field-grid-3">
        <label className="mrc-campo"><span>Data em que caiu</span><input className="input" type="date" max={hoje} value={data} onChange={(e) => setData(e.target.value)} /></label>
        <label className="mrc-campo"><span>Valor que caiu (R$)</span><input className="input" type="number" min="0.01" step="0.01" value={valor} onChange={(e) => setValor(e.target.value)} /></label>
        <label className="mrc-campo"><span>Para este fechamento (R$)</span><input className="input" type="number" min="0.01" step="0.01" placeholder={brl(Math.min(v, Math.max(0, x.S)))} value={aqui} onChange={(e) => setAqui(e.target.value)} /></label>
      </div>
      <label className="mrc-campo"><span>Observação (opcional)</span><input className="input" type="text" value={obs} onChange={(e) => setObs(e.target.value)} /></label>
      <div className="mlv-sit-det">Conta: {contaNome(x.loja.conta_id)} (a de recebimento da loja — dinheiro de outra conta não se liga a ela). Pagador: {x.loja.plataforma}.</div>
      <div className="mrc-botoes">
        <Button size="sm" variant="ghost" onClick={onVoltar}>Voltar</Button>
        <Button size="sm" variant="primary" icon="check" pede={['criar', 'editar']} disabled={!pode} onClick={gravar}>{salvando ? 'Registrando…' : 'Registrar e ligar'}</Button>
      </div>
    </section>
  );
}

// "Conferir diferença": o saldo dividido em linhas tipo + valor. Começa VAZIO (o app nunca põe o total em "taxa").
function MktRecClassificar({ x, salvando, setSalvando, fim, onCancelar }) {
  const [linhas, setLinhas] = React.useState([{ tipo: '', valor: '', observacao: '' }]);
  const soma = mktRecR2(linhas.reduce((a, l) => a + (Number(l.valor) || 0), 0));
  const resta = mktRecR2(x.S - soma);
  const set = (i, k, v) => setLinhas((ls) => ls.map((l, j) => (j === i ? { ...l, [k]: v } : l)));
  const ok = linhas.length > 0 && linhas.every((l) => l.tipo && Number(l.valor) !== 0 && !isNaN(Number(l.valor)) && (Number(l.valor) > 0 || l.tipo === 'ajuste'));
  const gravar = async () => {
    setSalvando(true);
    const itens = linhas.map((l) => ({ tipo: l.tipo, valor: mktRecR2(l.valor), observacao: l.observacao || null }));
    await fim(await mktRecClassificar(x.id, itens), x.decl
      ? `Classificado R$ ${brl(soma)}: só identifica — a retenção do mês está na declaração ${mktRotuloDeclaracao(x.decl)}.`
      : `Classificado R$ ${brl(soma)}: entra no DRE de Relatórios como "Taxa de marketplace" (o tipo fica na linha). O saldo das contas não muda.`);
  };
  const d = x.decl;
  return (
    <section className="pr-secao mrc-modo" aria-label="Conferir diferença">
      <div className="pr-secao-titulo">Conferir diferença · saldo R$ {brl(x.S)}</div>
      <div className="pr-nota">{d
        ? <>Mês com declaração ({mktRotuloDeclaracao(d)}): classificar aqui só IDENTIFICA — nada é lançado. Da declaração do mês (a loja inteira): taxas R$ {brl(Math.abs(Number(d.taxas) || 0))} · cupons e frete R$ {brl(Math.abs(Number(d.cupons_rebates) || 0) + Math.abs(Number(d.subtotal_frete) || 0))} · frete do comprador R$ {brl(d.frete_comprador)} · liberado R$ {brl(d.pagamento_liberado)}.</>
        : <>Cada linha vira "Taxa de marketplace" em Contas a Pagar, Pago, SEM conta e SEM data — a plataforma reteve antes de o dinheiro entrar: entra no DRE de Relatórios e não mexe no saldo de conta nenhuma. O ajuste negativo só absorve o que passou do esperado.</>}</div>
      {linhas.map((l, i) => (
        <div key={i} className="mrc-dif">
          <select className="input" aria-label="Tipo da diferença" value={l.tipo} onChange={(e) => set(i, 'tipo', e.target.value)}>
            <option value="">Escolha o tipo…</option>
            {MKT_REC_TIPOS.map((t) => <option key={t.id} value={t.id}>{t.rotulo}</option>)}
          </select>
          <input className="input mrc-num" type="number" step="0.01" aria-label="Valor da diferença" value={l.valor} onChange={(e) => set(i, 'valor', e.target.value)} />
          <button type="button" className="fc-linkbtn" onClick={() => set(i, 'valor', String(mktRecR2((Number(l.valor) || 0) + resta)))}>usar o restante</button>
          <input className="input" type="text" placeholder="Observação (opcional)" aria-label="Observação da diferença" value={l.observacao} onChange={(e) => set(i, 'observacao', e.target.value)} />
          {linhas.length > 1 && <button type="button" className="fc-linkbtn" onClick={() => setLinhas((ls) => ls.filter((_, j) => j !== i))}>tirar</button>}
        </div>))}
      <div className="mlv-sit-det">Sem classificar depois disto: <b>R$ {brl(resta)}</b>{resta > 0.004 ? ' — fica como saldo (dinheiro que ainda vai chegar, ou diferença para depois).' : ''}</div>
      <div className="mrc-botoes">
        <Button size="sm" variant="ghost" icon="plus" onClick={() => setLinhas((ls) => ls.concat([{ tipo: '', valor: '', observacao: '' }]))}>Outra linha</Button>
        <span style={{ flex: 1 }} />
        <Button size="sm" variant="secondary" onClick={onCancelar}>Cancelar</Button>
        <Button size="sm" variant="primary" icon="check" pede="editar" disabled={!ok || salvando} onClick={gravar}>{salvando ? 'Gravando…' : `Classificar R$ ${brl(soma)}`}</Button>
      </div>
    </section>
  );
}

// ── Recebimentos › Por fechamento ───────────────────────────────────────────────────────────────────────────────
function MktRecebimentos({ onNav }) {
  const fm = useMktFiltros();
  const f = mktFechFiltroServidor(fm);
  const [reloadKey, setReloadKey] = React.useState(0);
  const d = useMktRecDados(f, reloadKey, true);
  const [filtro, setFiltro] = React.useState('fila');
  const [abertoId, setAbertoId] = React.useState(null);
  const [verFora, setVerFora] = React.useState(false);
  const [verMes, setVerMes] = React.useState(false);
  const ok = !d.lendo && !d.erro && Array.isArray(d.fech);
  const linhas = ok ? mktRecResumo({ fechamentos: d.fech.filter((p) => p.origem !== 'mercadolivre'), lojas: d.lojas, contas: d.contas, recsLegado: d.legados,
    taxas: d.taxas, decls: d.decls, vinculos: d.conf.vinculos, diferencas: d.conf.diferencas }) : [];
  const dinheiros = ok ? mktRecDinheiros(d.recs, d.conf.vinculos) : [];
  const lojaPor = {}; (d.lojas || []).forEach((l) => { lojaPor[l.id] = l; });
  const contaPor = {}; (d.contas || []).forEach((c) => { contaPor[c.id] = c; });
  const fila = linhas.filter((x) => x.sit !== 'fora_do_caixa');
  const fora = linhas.filter((x) => x.sit === 'fora_do_caixa');
  const conta = {}; MKT_REC_FILTROS.forEach(([id]) => { conta[id] = linhas.filter((x) => mktRecPassaFiltro(x, id)).length; });
  const visiveis = linhas.filter((x) => mktRecPassaFiltro(x, filtro));
  const aberto = abertoId ? linhas.find((x) => x.id === abertoId) || null : null;
  const semPag = ok ? mktRecSemPagador(d.recs) : { n: 0, soma: 0 };
  const porMes = ok ? mktRecPorLojaMes(linhas, d.decls) : [];
  // Mercado Livre: o previsto por loja (o total dos pedidos no período) — sem vínculo, sem valor de liberação
  const ml = {}; (d.ml || []).forEach((p) => { const o = ml[p.marketplace_loja_id] || (ml[p.marketplace_loja_id] = { n: 0, total: 0 }); o.n++; o.total = mktRecR2(o.total + (Number(p.total) || 0)); });
  const soma = (l, k) => mktRecR2(l.reduce((a, x) => a + x[k], 0));
  // vindo de Vendas › Fechamentos: abre o fechamento pedido (uma vez)
  React.useEffect(() => {
    if (!ok) return;
    let id = null;
    try { id = window.sessionStorage.getItem(MKT_REC_ABRIR); window.sessionStorage.removeItem(MKT_REC_ABRIR); } catch (e) { id = null; }
    if (id) setAbertoId(id);
  }, [ok]);
  const colunas = [
    { id: 'fechamento', rotulo: 'Fechamento', fixa: true, valor: (x) => x.p.data_venda || '', render: (x) => (
      <div><span className="ref-mono gold">{x.p.numero}</span><div className="mlv-sit-det">{x.loja ? x.loja.nome : '—'} · {(x.p.periodo_inicio && x.p.periodo_fim) ? periodoLabel(x.p.periodo_inicio, x.p.periodo_fim) : fmtDate(x.p.data_venda)}</div></div>) },
    { id: 'esperado', rotulo: 'Esperado', num: true, valor: (x) => x.P, render: (x) => 'R$ ' + brl(x.P) },
    { id: 'recebido', rotulo: 'Recebido', num: true, valor: (x) => x.R, render: (x) => (x.R ? 'R$ ' + brl(x.R) : <span className="muted">—</span>) },
    { id: 'diferencas', rotulo: 'Diferenças', num: true, valor: (x) => x.D, render: (x) => (x.D ? 'R$ ' + brl(x.D) : <span className="muted">—</span>) },
    { id: 'saldo', rotulo: 'Saldo', num: true, valor: (x) => x.S, render: (x) => (Math.abs(x.S) < 0.005 ? <span className="muted">R$ 0,00</span> : 'R$ ' + brl(x.S)) },
    { id: 'situacao', rotulo: 'Situação', valor: (x) => (MKT_REC_SITUACOES[x.sit] || {}).rotulo || x.sit, render: (x) => (
      <div className="mlv-sit"><Badge tone={(MKT_REC_SITUACOES[x.sit] || {}).tom}>{(MKT_REC_SITUACOES[x.sit] || {}).rotulo || x.sit}</Badge>
        {x.classificada && <div className="mlv-sit-det">diferença classificada</div>}</div>) },
    { id: 'conta', rotulo: 'Conta', valor: (x) => (x.conta ? x.conta.nome : ''), render: (x) => <span className="muted">{x.conta ? x.conta.nome : 'loja sem conta'}</span> },
  ];
  const mlLojas = (d.lojas || []).filter((l) => l.plataforma === 'Mercado Livre' && fm.passaLoja(l.id) && l.situacao !== 'Arquivada' && (!fm.plataforma || fm.plataforma === 'Mercado Livre'));
  return (
    <div className={fm.dentroDoHub ? 'mkt-tela' : 'canvas'}>
      <PageHead title={mktTituloDaTela(fm, 'Marketplace · Recebimentos')}
        sub="O dinheiro que já está no caixa, ligado a cada fechamento, e a diferença classificada. Ligar não muda o caixa: o dinheiro já está lá."
        right={<Button variant="ghost" icon="refresh-cw" size="sm" onClick={() => setReloadKey((k) => k + 1)}>Atualizar a tela</Button>} />
      {d.erro && <EstadoDados estado="erro" titulo="Não consegui ler os recebimentos" mensagem={d.erro + ' — nada foi gravado, e as ações ficam travadas até a leitura voltar.'} aoTentar={() => setReloadKey((k) => k + 1)} />}
      {ok && d.conf.instalado === false && <EstadoDados estado="vazio" titulo="O banco ainda não tem os vínculos de Recebimentos" mensagem="O SUPABASE_marketplace-loteD.sql ainda não rodou: a lista mostra o esperado; ligar e classificar ficam para depois dele." compacto />}
      {ok && (
        <>
          <div className="mrc-resumo">
            <span><b>{fila.length}</b> fechamento{fila.length !== 1 ? 's' : ''} na fila · esperado R$ {brl(soma(fila, 'P'))} · recebido R$ {brl(soma(fila, 'R'))} · saldo R$ {brl(soma(fila, 'S'))}</span>
            {fora.length > 0 && <button type="button" className="fc-linkbtn" aria-expanded={verFora} onClick={() => setVerFora((v) => !v)}>
              {fora.length} declarado{fora.length !== 1 ? 's' : ''} · fora do caixa do app (R$ {brl(soma(fora, 'P'))}) — {verFora ? 'esconder' : 'ver'}</button>}
            {porMes.length > 0 && <button type="button" className="fc-linkbtn" aria-expanded={verMes} onClick={() => setVerMes((v) => !v)}>{verMes ? 'Esconder' : 'Ver'} por loja e mês (o liberado da declaração)</button>}
          </div>
          {semPag.n > 0 && (
            <div className="mkt-nota mrc-pendencia"><Icon name="alert-circle" size={14} />
              <span>{semPag.n} recebimento{semPag.n !== 1 ? 's' : ''} sem pagador de plataforma (R$ {brl(semPag.soma)}) — se forem da Shopee ou do TikTok, acerte o pagador no Financeiro: só dinheiro com o pagador da plataforma se liga a fechamento.
                {' '}<button type="button" className="fc-linkbtn" onClick={() => onNav && onNav('financeiro/contas-receber')}>Contas a Receber</button></span></div>)}
          {mlLojas.map((l) => (
            <div key={l.id} className="mkt-nota mrc-ml"><Icon name="info" size={14} />
              <span><b>{l.nome}</b> (Mercado Livre): esperado R$ {brl((ml[l.id] || {}).total || 0)} em {(ml[l.id] || {}).n || 0} venda{((ml[l.id] || {}).n || 0) !== 1 ? 's' : ''} no período · <b>liberação e recebimento: Fase 2</b> (Mercado Pago). Sem vínculo e sem valor aqui: o dinheiro do ML cai no saldo do Mercado Pago, misturado, e a liberação por venda é da API dele.</span></div>))}
          {verMes && (
            <div className="td-caixa mrc-mes"><table className="tbl"><thead><tr><th>Loja</th><th>Mês</th><th className="td-num">Esperado</th><th className="td-num">Recebido (ligado)</th><th className="td-num">Liberado (declaração)</th></tr></thead>
              <tbody>{porMes.map((o) => (<tr key={o.loja.id + o.mes}><td>{o.loja.nome}</td><td>{o.mes.slice(5) + '/' + o.mes.slice(0, 4)}</td><td className="td-num">R$ {brl(o.esperado)}</td>
                <td className="td-num">R$ {brl(o.recebido)}</td><td className="td-num">{o.liberado == null ? <span className="muted">sem declaração</span> : 'R$ ' + brl(o.liberado)}</td></tr>))}</tbody></table>
              <div className="mlv-sit-det">O liberado é da loja no mês inteiro (por data de liberação); o esperado e o recebido são dos fechamentos do mês (por data de venda). Não fecham mês a mês, e não deveriam: entre liberar e sacar fica o saldo da carteira na plataforma.</div></div>)}
          <div className="mla-barra">
            <div className="mla-chips" role="group" aria-label="Situação do recebimento">
              {MKT_REC_FILTROS.map(([id, rot]) => <Chip key={id} active={filtro === id} onClick={() => setFiltro(id)}>{rot} ({conta[id] || 0})</Chip>)}
            </div>
          </div>
        </>
      )}
      <TabelaDados idEstado="mkt-recebimentos" rotulo="Recebimentos por fechamento" colunas={colunas} linhas={verFora ? fora : visiveis} chave={(x) => x.id}
        estado={d.lendo && !linhas.length ? 'carregando' : undefined} erro={d.erro ? 'Não consegui ler: ' + d.erro : ''} aoTentar={() => setReloadKey((k) => k + 1)}
        mensagemVazio={verFora ? 'Nenhum fechamento fora do caixa do app neste filtro.' : 'Nenhum fechamento com este filtro.'}
        onAbrir={(x) => setAbertoId(x.id)} rotuloLinha={(x) => 'Fechamento ' + x.p.numero} />
      {aberto && <PainelRecFechamento x={aberto} dinheiros={dinheiros} lojas={d.lojas} contaPor={contaPor} onNav={onNav}
        onFechar={() => setAbertoId(null)} onGravou={() => setReloadKey((k) => k + 1)} />}
    </div>
  );
}

// ── Recebimentos › Por dinheiro: os saques das plataformas no caixa, o já ligado e o livre ──────────────────────
function PainelRecDinheiro({ dd, linhas, lojaPor, contaPor, onFechar, onGravou }) {
  const [sel, setSel] = React.useState({});
  const [msg, setMsg] = React.useState('');
  const [salvando, setSalvando] = React.useState(false);
  const contaNome = (id) => (contaPor[id] ? contaPor[id].nome : 'sem conta');
  // os fechamentos onde ESTE dinheiro cabe: lojas cuja conta o recebeu e cujo pagador é este; do mais próximo ao mais longe
  const alvos = linhas.filter((x) => x.loja && x.loja.conta_id === dd.contaId && !x.legados.length && x.sit !== 'fora_do_caixa'
    && mktRecCasaLoja(x.loja, dd.r) && String(x.p.periodo_inicio || x.p.data_venda) <= dd.data && mktRecCabe(x) > 0.004)
    .sort((a, b) => String(b.p.periodo_fim || b.p.data_venda).localeCompare(String(a.p.periodo_fim || a.p.data_venda)));
  const total = mktRecR2(Object.values(sel).reduce((a, v) => a + (Number(v) || 0), 0));
  const invalido = Object.entries(sel).some(([id, v]) => { const x = alvos.find((y) => y.id === id); return !(Number(v) > 0) || !x || Number(v) > mktRecCabe(x) + 0.004; });
  const ligar = async () => {
    setSalvando(true);
    const r = await mktRecLigar(dd.id, Object.entries(sel).map(([id, v]) => ({ pedido_id: id, valor: mktRecR2(v) })));
    setSalvando(false);
    if (r.error) { setMsg('Nada foi gravado: ' + mktRecErro(r.error)); return; }
    setMsg(`Ligado R$ ${brl(total)} a ${Object.keys(sel).length} fechamento(s), numa transação só. Nada mudou no caixa.`); setSel({}); onGravou();
  };
  const estornar = async (v, motivo) => {
    setSalvando(true);
    const r = await mktRecEstornarVinculo(v.id, motivo);
    setSalvando(false);
    if (r.error) { setMsg('Nada foi gravado: ' + mktRecErro(r.error)); return; }
    setMsg(`Vínculo de R$ ${brl(v.valor)} estornado: o dinheiro ficou livre.`); onGravou();
  };
  const fechPor = {}; linhas.forEach((x) => { fechPor[x.id] = x; });
  return (
    <PainelRegistro aberto={true} onFechar={onFechar} eyebrow="Dinheiro no caixa" titulo={'R$ ' + brl(dd.entrou) + ' em ' + fmtDate(dd.data)}
      subtitulo={contaNome(dd.contaId) + ' · ' + (dd.r.cliente_nome || '') } largura={680}
      acoes={[{ id: 'ligar', rotulo: salvando ? 'Ligando…' : `Ligar R$ ${brl(total)}`, icone: 'link', pede: 'editar', desabilitado: salvando || !Object.keys(sel).length || invalido || total > dd.livre + 0.004, onClick: ligar }]}>
      {msg && <div className="card mla-msg" role="status">{msg}</div>}
      <section className="pr-secao" aria-label="O dinheiro">
        <div className="mrc-valores">
          <div><div className="pr-rot">Entrou</div><b>R$ {brl(dd.entrou)}</b></div>
          <div><div className="pr-rot">Ligado</div><b>R$ {brl(dd.ligado)}</b></div>
          <div><div className="pr-rot">Livre</div><b>R$ {brl(dd.livre)}</b></div>
          <div><div className="pr-rot">Data</div><b>{fmtDate(dd.data)}</b></div>
        </div>
        <div className="mlv-sit-det">{dd.r.referencia || ''}{dd.r.observacoes ? ' · ' + dd.r.observacoes : ''}</div>
      </section>
      <section className="pr-secao" aria-label="Ligado a">
        <div className="pr-secao-titulo">Ligado a</div>
        {dd.vinculos.filter((v) => !v.estornado_em).length ? dd.vinculos.filter((v) => !v.estornado_em).map((v) => (
          <div key={v.id} className="mrc-item"><span>{fechPor[v.pedido_id] ? fechPor[v.pedido_id].p.numero : 'fechamento fora do filtro'}</span>
            <span><b>R$ {brl(v.valor)}</b> · ligado em {fmtDatetime(v.criado_em)}</span>
            <MktRecEstornar rotulo={'R$ ' + brl(v.valor)} salvando={salvando} onConfirmar={(m) => estornar(v, m)} /></div>))
          : <span className="muted">nenhum fechamento</span>}
      </section>
      {dd.livre > 0.004 && (
        <section className="pr-secao" aria-label="Distribuir">
          <div className="pr-secao-titulo">Distribuir por fechamentos (um saque para várias semanas)</div>
          {alvos.length ? alvos.map((x) => (
            <label key={x.id} className="mrc-cand">
              <input type="checkbox" checked={sel[x.id] !== undefined} onChange={() => setSel((s) => { const n = { ...s }; if (n[x.id] !== undefined) delete n[x.id];
                else { const usado = Object.values(n).reduce((a, v) => a + (Number(v) || 0), 0); n[x.id] = String(mktRecR2(Math.max(0, Math.min(Math.max(0, x.S), dd.livre - usado)))); } return n; })} />
              <span className="mrc-cand-txt"><b>{x.p.numero}</b> · {x.loja.nome} · {(x.p.periodo_inicio && x.p.periodo_fim) ? periodoLabel(x.p.periodo_inicio, x.p.periodo_fim) : fmtDate(x.p.data_venda)}
                <span className="mlv-sit-det"> · esperado R$ {brl(x.P)} · falta R$ {brl(Math.max(0, x.S))}</span>{mktRecRefCasa(dd.r, x.p) && <Badge tone="info">a referência cita este período</Badge>}</span>
              {sel[x.id] !== undefined && <input className="input mrc-num" type="number" min="0.01" step="0.01" aria-label={'Valor para ' + x.p.numero}
                value={sel[x.id]} onChange={(e) => setSel((s) => ({ ...s, [x.id]: e.target.value }))} />}
            </label>))
            : <EstadoDados estado="vazio" titulo="Nenhum fechamento onde este dinheiro cabe" mensagem="Só fechamentos da loja cuja conta recebeu este dinheiro, do pagador dele, que começaram antes da data e ainda têm saldo." compacto />}
          {total > dd.livre + 0.004 && <div className="mlv-sit-det mrc-aviso">Passa do livre deste dinheiro (R$ {brl(dd.livre)}).</div>}
        </section>)}
    </PainelRegistro>
  );
}

function MktRecDinheiro({ onNav }) {   // eslint-disable-line no-unused-vars
  const fm = useMktFiltros();
  const f = mktFechFiltroServidor({ ...fm, periodo: null });   // os fechamentos de todas as datas (o dinheiro é de hoje; a semana, de antes)
  const [reloadKey, setReloadKey] = React.useState(0);
  const d = useMktRecDados(f, reloadKey, false);
  const [filtro, setFiltro] = React.useState('livre');
  const [abertoId, setAbertoId] = React.useState(null);
  const ok = !d.lendo && !d.erro && Array.isArray(d.fech);
  const linhas = ok ? mktRecResumo({ fechamentos: d.fech, lojas: d.lojas, contas: d.contas, recsLegado: d.legados, taxas: d.taxas, decls: d.decls,
    vinculos: d.conf.vinculos, diferencas: d.conf.diferencas }) : [];
  const lojaPor = {}; (d.lojas || []).forEach((l) => { lojaPor[l.id] = l; });
  const contaPor = {}; (d.contas || []).forEach((c) => { contaPor[c.id] = c; });
  // as contas das lojas que o filtro deixa passar: o dinheiro é da conta (ele ainda não tem loja até ser ligado)
  const contasDoFiltro = new Set((d.lojas || []).filter((l) => fm.passaLoja(l.id) && l.conta_id).map((l) => l.conta_id));
  const plat = fm.plataforma === 'Shopee' ? 'shopee' : fm.plataforma === 'TikTok Shop' ? 'tiktok' : '';
  const todos = ok ? mktRecDinheiros(d.recs, d.conf.vinculos) : [];
  const dasPlataformas = todos.filter((x) => (x.pagador === 'shopee' || x.pagador === 'tiktok') && (!plat || x.pagador === plat)
    && (!fm.filtrando || contasDoFiltro.has(x.contaId)) && fm.passaData(x.data));
  const doML = todos.filter((x) => x.pagador === 'mercado livre');
  const visiveis = dasPlataformas.filter((x) => (filtro === 'livre' ? x.livre > 0.004 : filtro === 'ligado' ? x.livre <= 0.004 : true));
  const aberto = abertoId ? todos.find((x) => x.id === abertoId) || null : null;
  const PLAT = { shopee: 'Shopee', tiktok: 'TikTok Shop', 'mercado livre': 'Mercado Livre' };
  const colunas = [
    { id: 'data', rotulo: 'Data', fixa: true, valor: (x) => x.data, render: (x) => fmtDate(x.data) },
    { id: 'conta', rotulo: 'Conta', valor: (x) => (contaPor[x.contaId] || {}).nome || '', render: (x) => (contaPor[x.contaId] ? contaPor[x.contaId].nome : <span className="muted">sem conta</span>) },
    { id: 'pagador', rotulo: 'Pagador', valor: (x) => PLAT[x.pagador] || '', render: (x) => <span>{PLAT[x.pagador]}<div className="mlv-sit-det">{x.r.referencia || x.r.cliente_nome || ''}</div></span> },
    { id: 'entrou', rotulo: 'Entrou', num: true, valor: (x) => x.entrou, render: (x) => 'R$ ' + brl(x.entrou) },
    { id: 'ligado', rotulo: 'Ligado', num: true, valor: (x) => x.ligado, render: (x) => (x.ligado ? 'R$ ' + brl(x.ligado) : <span className="muted">—</span>) },
    { id: 'livre', rotulo: 'Livre', num: true, valor: (x) => x.livre, render: (x) => (x.livre > 0.004 ? <b>R$ {brl(x.livre)}</b> : <span className="muted">R$ 0,00</span>) },
    { id: 'n', rotulo: 'Fechamentos', num: true, valor: (x) => x.vinculos.filter((v) => !v.estornado_em).length },
  ];
  return (
    <div className={fm.dentroDoHub ? 'mkt-tela' : 'canvas'}>
      <PageHead title={mktTituloDaTela(fm, 'Marketplace · Recebimentos')} sub="Os saques da Shopee e do TikTok no caixa: quanto já está ligado a fechamentos e quanto está livre. Um saque pode ir para várias semanas."
        right={<Button variant="ghost" icon="refresh-cw" size="sm" onClick={() => setReloadKey((k) => k + 1)}>Atualizar a tela</Button>} />
      {d.erro && <EstadoDados estado="erro" titulo="Não consegui ler os recebimentos" mensagem={d.erro} aoTentar={() => setReloadKey((k) => k + 1)} />}
      {ok && doML.length > 0 && (
        <div className="mkt-nota mrc-ml"><Icon name="info" size={14} />
          <span>Mercado Pago / Mercado Livre: {doML.length} recebimento{doML.length !== 1 ? 's' : ''} no caixa (R$ {brl(doML.reduce((a, x) => a + x.entrou, 0))}) — <b>aguardam a Fase 2</b>. Não se ligam às vendas do ML: são os agregados do mês, e dividi-los por venda seria palpite.</span></div>)}
      {ok && (
        <div className="mla-barra"><div className="mla-chips" role="group" aria-label="Situação do dinheiro">
          {[['livre', 'Com saldo livre'], ['ligado', 'Todo ligado'], ['todos', 'Todos']].map(([id, rot]) => (
            <Chip key={id} active={filtro === id} onClick={() => setFiltro(id)}>{rot} ({dasPlataformas.filter((x) => (id === 'livre' ? x.livre > 0.004 : id === 'ligado' ? x.livre <= 0.004 : true)).length})</Chip>))}
        </div></div>)}
      <TabelaDados idEstado="mkt-rec-dinheiro" rotulo="Recebimentos por dinheiro" colunas={colunas} linhas={visiveis} chave={(x) => x.id}
        estado={d.lendo && !todos.length ? 'carregando' : undefined} erro={d.erro ? 'Não consegui ler: ' + d.erro : ''} aoTentar={() => setReloadKey((k) => k + 1)}
        mensagemVazio="Nenhum saque da Shopee ou do TikTok com este filtro." onAbrir={(x) => setAbertoId(x.id)} rotuloLinha={(x) => 'Dinheiro de ' + fmtDate(x.data)} />
      {aberto && <PainelRecDinheiro dd={aberto} linhas={linhas} lojaPor={lojaPor} contaPor={contaPor}
        onFechar={() => setAbertoId(null)} onGravou={() => setReloadKey((k) => k + 1)} />}
    </div>
  );
}

Object.assign(window, {
  MktRecebimentos, MktRecDinheiro, PainelRecFechamento, PainelRecDinheiro,
  MKT_REC_JANELA_DIAS, MKT_REC_TIPOS, MKT_REC_SITUACOES, MKT_REC_ABRIR, mktRecAbrirDepois,
  mktRecR2, mktRecMaisDias, mktRecPagador, mktRecCasaLoja, mktRecEhDinheiro, mktRecDinheiros, mktRecRefCasa, mktRecCandidatos,
  mktRecEmOutrasContas, mktRecResumo, mktRecPassaFiltro, mktRecCabe, mktRecSemPagador, mktRecPorLojaMes,
});
