/* global React, Icon, Button, Badge, Chip, PageHead, EstadoDados, brl, fmtDate, fmtDatetime, useMktFiltros, mktTituloDaTela, lerTodas,
   saldoDaConta */
// ============================================================
// Marketplace_ExtratoMP.jsx — Marketplace › Recebimentos › Extrato do MP (F2b, 15/10/2026)
//
// O relatório "Liberações" do Mercado Pago entra no caixa, conta por conta e mês a mês, com a PROVA do saldo. A palavra do PM
// (CLAUDECODE_mp-f2b-passo0-palavra.md e CLAUDECODE_mp-f2b-csv-chegou.md):
//  1. a fonte é o "Liberações" em CSV, por conta e mês (o único com o saldo depois de cada linha); prévia + sha256 (o mesmo
//     arquivo de novo = "já importado, igual"); NADA grava antes da confirmação;
//  2. a PROVA para a importação quando o saldo do fim do mês não fecha (e quando uma operação não casa), nomeando — sem
//     "importar mesmo assim": diferença é defeito a entender;
//  3. HERANÇA: a linha herda a categoria e a natureza do lançamento do app que ela substitui; o que não tinha lançamento vai
//     à fila; a importação nunca reclassifica sozinha — a prévia diz em letras o que sobra de cada lançamento substituído;
//  4. os pares de soma zero da F2a ficam; a conferência aceita "operação ausente do relatório com soma zero" como igual;
//  5. o dia do relatório corrige o da API (marca `dia_do_relatorio`): a sincronização não volta a mexer nesse dia.
//  E: a fatura do ML entra na data do débito como "Taxa de marketplace" (o Lote E a separa por código); id de operação
//  alfanumérico é aceito (a fatura vem assim); saque com a conta de destino no app vira transferência; `reserve_for_*` é
//  par que soma zero (não vira título).
// O que não é estilo:
//  - quem decide é o BANCO (`mp_f2b_importar`): ele confere o arquivo, o plano e a prova de novo, e grava tudo ou nada. A
//    prévia aqui é a MESMA regra (as funções puras abaixo; a PROVA_mp-f2b.js e a réplica rodam as duas sobre os mesmos
//    dados) — ela explica antes; não é ela que autoriza;
//  - o saldo da prova é o `saldoDaConta` de Contas, com a data do fim do mês: a mesma régua do Saldo da tela;
//  - a lista de dependências dos efeitos é só texto estável (a conta, o mês, a chave de recarga).
// ============================================================

// ── a régua da F2b (funções puras) ── início ─────────────────────────────────────────────────────────────────────
const MP_REL_COLUNAS = ['DATE', 'SOURCE_ID', 'DESCRIPTION', 'NET_CREDIT_AMOUNT', 'NET_DEBIT_AMOUNT', 'BALANCE_AMOUNT'];
const MP_REL_FILA_CATEGORIA = 'A classificar (extrato do Mercado Pago)';
const MP_REL_FATURA_CATEGORIA = 'Taxa de marketplace';
const MP_REL_JANELA_DIAS = 3;        // o lançamento ÚNICO casa com a linha a até 3 dias (a fatura 03 → 04/08, o boleto 18 → 19/08)
const MP_REL_JANELA_TRANSF = 5;      // o outro lado da transferência: até 5 dias (a mesma janela do banco)
const MP_REL_MAX_LINHAS = 6;         // o agregado se decompõe em até 6 linhas pela soma exata; mais que isso, só pelo resto
function mpRelCent(x) { return Math.round((Number(x) || 0) * 100); }
function mpRelReal(c) { return Math.round(Number(c) || 0) / 100; }
// número do CSV: "9742.74" (o do painel); "9742,74" e "9.742,74" também (o CSV salvo por uma planilha em português)
function mpRelNum(s) {
  let x = String(s == null ? '' : s).trim();
  if (x === '') return 0;
  const v = x.lastIndexOf(','), p = x.lastIndexOf('.');
  if (v >= 0 && p >= 0) x = v > p ? x.replace(/\./g, '').replace(',', '.') : x.replace(/,/g, '');
  else if (v >= 0) x = x.replace(',', '.');
  return Number(x);
}
// o dia em São Paulo, AAAA-MM-DD (o banco usa America/Sao_Paulo; o Brasil não tem horário de verão desde 2019: UTC−3)
function mpRelDia(iso) {
  const t = Date.parse(String(iso || ''));
  return isFinite(t) ? new Date(t - 3 * 3600000).toISOString().slice(0, 10) : null;
}
function mpRelFimDoMes(mes) { const [a, m] = mes.split('-').map(Number); return new Date(Date.UTC(a, m, 0)).toISOString().slice(0, 10); }
function mpRelMesAnterior(mes) { const [a, m] = mes.split('-').map(Number); return new Date(Date.UTC(a, m - 2, 1)).toISOString().slice(0, 7); }
function mpRelMaisDias(ymd, n) { const t = new Date(ymd + 'T12:00:00Z'); t.setUTCDate(t.getUTCDate() + n); return t.toISOString().slice(0, 10); }
function mpRelDistDias(a, b) { return Math.round(Math.abs(Date.parse(a + 'T12:00:00Z') - Date.parse(b + 'T12:00:00Z')) / 86400000); }
function mpRelMesRotulo(mes) {
  const nomes = ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'];
  return nomes[Number(mes.slice(5, 7)) - 1] + '/' + mes.slice(0, 4);
}
// separa uma linha do CSV (com aspas, se vierem)
function mpRelSepara(linha, sep) {
  const out = []; let cur = '', aspas = false;
  for (let i = 0; i < linha.length; i++) {
    const ch = linha[i];
    if (aspas) { if (ch === '"') { if (linha[i + 1] === '"') { cur += '"'; i++; } else aspas = false; } else cur += ch; }
    else if (ch === '"') aspas = true;
    else if (ch === sep) { out.push(cur); cur = ''; }
    else cur += ch;
  }
  out.push(cur);
  return out;
}
// LÊ o "Liberações": as colunas pelo NOME (o painel pode trazer mais ou em outra ordem), o total do fim (sem data) sai e fica
// para conferir, e a 1ª linha é o saldo inicial (sem id, dia 1 às 00:00). Não confere o saldo corrido — é a prévia que confere
// (a mesma regra do banco), para o erro sair com o número da linha.
function mpRelLerCsv(texto, nome) {
  const t = String(texto || '').replace(/^\uFEFF/, '');
  const brutas = t.split(/\r?\n/).filter((l) => l.trim() !== '');
  if (!brutas.length) return { erro: 'O arquivo está vazio.' };
  const sep = brutas[0].split(';').length >= brutas[0].split(',').length ? ';' : ',';
  const cab = mpRelSepara(brutas[0], sep).map((c) => c.trim().toUpperCase());
  const falta = MP_REL_COLUNAS.filter((c) => cab.indexOf(c) < 0);
  if (falta.length) return { erro: 'Não é o relatório "Liberações" do Mercado Pago: faltam as colunas ' + falta.join(', ') + '.' };
  const idx = {}; cab.forEach((c, i) => { idx[c] = i; });
  const linhas = []; let total = null;
  for (let i = 1; i < brutas.length; i++) {
    const c = mpRelSepara(brutas[i], sep);
    const get = (k) => (idx[k] == null ? '' : String(c[idx[k]] == null ? '' : c[idx[k]]).trim());
    const data = get('DATE');
    const cr = mpRelNum(get('NET_CREDIT_AMOUNT')), db = mpRelNum(get('NET_DEBIT_AMOUNT')), saldo = mpRelNum(get('BALANCE_AMOUNT'));
    if (!data) {
      if (i === brutas.length - 1) { total = { credito: cr, debito: db, saldo }; continue; }
      return { erro: 'Linha ' + (i + 1) + ' do arquivo sem data (só a última — o total — vem assim).' };
    }
    if (![cr, db, saldo].every((n) => isFinite(n))) return { erro: 'Linha ' + (i + 1) + ' do arquivo: um valor que não é número.' };
    linhas.push({ ordem: linhas.length + 1, data, dia: mpRelDia(data), operacao: get('SOURCE_ID'), descricao: get('DESCRIPTION'),
      credito: cr, debito: db, saldo, metodo: get('PAYMENT_METHOD') });
  }
  if (!linhas.length) return { erro: 'O arquivo não tem linha nenhuma depois do cabeçalho.' };
  const ini = linhas[0];
  if (ini.operacao || !ini.dia) return { erro: 'A 1ª linha não é o saldo inicial do mês (o "Liberações" a traz sem id, no dia 1 às 00:00).' };
  const numConta = (String(nome || '').match(/([0-9]{7,})/) || [])[1] || null;
  return { mes: ini.dia.slice(0, 7), linhas, total, numConta, colunas: cab, separador: sep };
}
// as subconjuntos de `vals` (centavos, positivos) que somam `alvo`, do MENOR tamanho que existir (até `max`), parando no 2º:
// a sugestão só aceita a decomposição ÚNICA
function mpRelSomaExata(vals, alvo, max) {
  const ord = vals.map((v, i) => ({ v, i })).sort((a, b) => b.v - a.v);
  const resto = []; for (let i = ord.length - 1, s = 0; i >= 0; i--) { s += ord[i].v; resto[i] = s; }
  for (let k = 1; k <= max; k++) {
    const achados = [];
    const busca = (ini, falta, esc) => {
      if (achados.length > 1) return;
      if (esc.length === k) { if (falta === 0) achados.push(esc.map((j) => ord[j].i)); return; }
      for (let j = ini; j < ord.length; j++) {
        if (resto[j] < falta) return;
        if (ord[j].v > falta) continue;
        busca(j + 1, falta - ord[j].v, esc.concat(j));
        if (achados.length > 1) return;
      }
    };
    busca(0, alvo, []);
    if (achados.length) return achados;
  }
  return [];
}
// A PRÉVIA: a mesma conferência do `mp_f2b_importar`, com o plano SUGERIDO (e as escolhas da tela por cima) e a prova do
// saldo simulada pelo `saldoDaConta` de Contas. Entradas: o relatório lido; a conta; os movimentos do MP da conta (todos);
// as transferências, os títulos a receber e a pagar e as entradas de empréstimo da conta; as entradas "transferência interna"
// das OUTRAS contas no mês (o outro lado do saque); os relatórios já importados da conta; os recebimentos ligados a fechamento
// (não se substituem); as categorias de despesa; e a função de saldo.
function mpRelPrevia(o) {
  const C = mpRelCent;
  const rel = o.rel, conta = o.conta || {}, L = rel.linhas;
  const mes = rel.mes, ini = mes + '-01', fim = mpRelFimDoMes(mes);
  const ancora = conta.data_saldo_inicial ? String(conta.data_saldo_inicial).slice(0, 10) : null;
  const abre = ancora && ancora > ini ? ancora : ini;
  const erros = [];
  if (conta.mp_user_id == null || conta.mp_user_id === '') erros.push('A conta "' + (conta.nome || '?') + '" não tem o número do Mercado Pago: o extrato só entra numa conta do Mercado Pago com a identidade definida.');
  else if (rel.numConta && String(rel.numConta) !== String(conta.mp_user_id)) erros.push('O arquivo é da conta ' + rel.numConta + ' do Mercado Pago, e a conta escolhida ("' + conta.nome + '") é a ' + conta.mp_user_id + '.');
  if (ancora && fim < ancora) erros.push('O livro da conta começa em ' + ancora.split('-').reverse().join('/') + ': ' + mes + ' é anterior.');
  const rels = Array.isArray(o.relatorios) ? o.relatorios : [];
  const jaImportado = rels.find((x) => x.mes === mes) || null;
  if (rels.some((x) => x.mes > mes)) erros.push('A conta já tem mês depois de ' + mpRelMesRotulo(mes) + ' importado: os meses entram em ordem (desfaça os seguintes antes).');
  if (ancora && ini > ancora && !rels.some((x) => x.mes === mpRelMesAnterior(mes))) erros.push('Importe ' + mpRelMesRotulo(mpRelMesAnterior(mes)) + ' antes: a abertura de ' + mpRelMesRotulo(mes) + ' é o fim do mês anterior, e é o relatório dele que a prova.');
  // o relatório, linha a linha (a mesma conferência do banco)
  const l1 = L[0];
  if (l1.operacao || l1.dia !== ini || C(l1.debito) !== 0 || C(l1.saldo) !== C(l1.credito) || String(l1.data).slice(11, 16) !== '00:00') erros.push('A 1ª linha tem que ser o saldo inicial de ' + mes + ' (dia 1, 00:00, sem id).');
  for (let i = 1; i < L.length; i++) {
    const l = L[i], ant = L[i - 1];
    if (!l.dia || l.dia < ini || l.dia > fim) { erros.push('Linha ' + l.ordem + ': a data não é de ' + mes + '.'); break; }
    if (!/^[0-9A-Za-z]{1,100}$/.test(l.operacao || '')) { erros.push('Linha ' + l.ordem + ': sem o id da operação, ou com caractere que um id do Mercado Pago não tem.'); break; }
    if (l.credito < 0 || l.debito < 0 || (C(l.credito) > 0 && C(l.debito) > 0)) { erros.push('Linha ' + l.ordem + ': crédito e débito têm que ser zero ou positivos, e só um deles.'); break; }
    if (C(l.saldo) !== C(ant.saldo) + C(l.credito) - C(l.debito)) { erros.push('Linha ' + l.ordem + ': o saldo do relatório não fecha com a linha anterior (o arquivo foi mexido?).'); break; }
  }
  const fimBanco = C(L[L.length - 1].saldo);
  let abBanco = C(l1.saldo);
  L.slice(1).forEach((l) => { if (l.dia && l.dia < abre) abBanco = C(l.saldo); });
  const sd = (ate, recs, pags, trs) => C(o.saldoDaConta(conta, recs, pags, trs, o.distrib || [], ate));
  const recs = o.recs || [], pags = o.pags || [], trs = o.transfs || [];
  const abApp = sd(mpRelMaisDias(abre, -1), recs, pags, trs);
  const fimAntes = sd(fim, recs, pags, trs);

  // as operações do livro (da âncora em diante)
  const ops = {}, ordemOps = [];
  L.slice(1).forEach((l) => {
    if (!l.dia || l.dia < abre) return;
    let x = ops[l.operacao];
    if (!x) { x = ops[l.operacao] = { op: l.operacao, net: 0, dia: null, diaMax: l.dia, descrs: [], payout: false, payment: false, primeira: l.ordem, linhas: [] }; ordemOps.push(x); }
    const v = C(l.credito) - C(l.debito);
    x.net += v; if (v !== 0) x.dia = l.dia; if (l.dia > x.diaMax) x.diaMax = l.dia;
    if (x.descrs.indexOf(l.descricao || '') < 0) x.descrs.push(l.descricao || '');
    if (l.descricao === 'payout') x.payout = true; if (l.descricao === 'payment') x.payment = true;
    x.linhas.push(l);
  });
  ordemOps.forEach((x) => { if (!x.dia) x.dia = x.diaMax; x.descrs.sort(); x.numerica = /^[0-9]{1,18}$/.test(x.op); });
  // o app: os movimentos da API por operação
  const api = {};
  (o.movs || []).forEach((m) => {
    if (m.origem !== 'api' || m.operacao_id == null) return;
    const k = String(m.operacao_id);
    const a = api[k] || (api[k] = { mes: 0, tot: 0, dias: {}, d0: null, corr: false, movs: [] });
    const v = C(m.valor), d = String(m.dia).slice(0, 10);
    a.tot += v; if (d >= abre && d <= fim) a.mes += v; a.dias[d] = true; if (!a.d0 || d < a.d0) a.d0 = d; if (m.dia_do_relatorio) a.corr = true; a.movs.push(m);
  });
  const temOp = (desc, op) => new RegExp('(^|[^0-9A-Za-z])' + op + '([^0-9A-Za-z]|$)').test(String(desc || ''));
  const valorTr = (t) => (t.conta_destino_id === conta.id ? C(t.valor) : -C(t.valor_origem != null ? t.valor_origem : t.valor));
  ordemOps.forEach((x) => {
    const a = api[x.op];
    if (a) {
      x.appMes = a.mes; x.appTotal = a.tot; x.appDia = a.d0; x.appDias = Object.keys(a.dias).length;
      if (!a.corr && x.net !== 0 && x.appDias === 1 && ((a.mes === x.net && a.d0 !== x.dia) || (a.mes !== x.net && a.tot === x.net))) {
        x.k = 'f2a_dia';
        x.movidoDeFora = a.movs.filter((m) => String(m.dia).slice(0, 10) > fim).reduce((s, m) => s + C(m.valor), 0);
      } else x.k = a.mes === x.net ? 'f2a' : 'f2a_dif';
      return;
    }
    const tr = trs.filter((t) => (t.conta_origem_id === conta.id || t.conta_destino_id === conta.id) && temOp(t.descricao, x.op));
    if (tr.length) { x.trValor = tr.reduce((s, t) => s + valorTr(t), 0); x.k = x.trValor === x.net ? 'transf' : 'transf_dif'; return; }
    x.k = x.net === 0 ? 'zero' : 'novo';
  });
  const novas = ordemOps.filter((x) => x.k === 'novo');

  // os lançamentos à mão da conta no mês que uma linha pode substituir
  const ligados = new Set(o.vinculosAtivos || []);
  const noMes = (r) => { const d = String(r.data_pagamento || '').slice(0, 10); return !!d && d >= abre && d <= fim; };
  const candPag = pags.filter((p) => p.conta_id === conta.id && p.status === 'Pago' && !p.gerado_por && !p.pedido_id && !p.colaborador_id && !p.aporte_id
    && !p.pedido_compra_id && !p.vendedor_id && noMes(p)).map((p) => ({ tabela: 'contas_pagar', id: p.id, r: p, valor: C(p.valor), dia: String(p.data_pagamento).slice(0, 10), sinal: -1 }));
  const candRec = recs.filter((r) => r.conta_id === conta.id && r.status === 'Recebido' && !r.gerado_por && !r.pedido_id && !r.devolucao_de_pagar_id
    && !ligados.has(r.id) && noMes(r)).map((r) => ({ tabela: 'contas_receber', id: r.id, r, valor: C(r.valor_recebido || r.valor), dia: String(r.data_pagamento).slice(0, 10), sinal: 1 }));
  const cands = candPag.concat(candRec);
  const candPor = {}; cands.forEach((c) => { candPor[c.id] = c; });

  // O PLANO SUGERIDO: (1) o lançamento único (mesmo valor, até 3 dias, sem ambiguidade nos dois sentidos); (2) o saque cujo
  // outro lado é uma entrada "transferência interna" em outra conta; (3) o agregado pela soma exata ÚNICA; (4) o último
  // lançamento de cada sentido fica com o resto (se o resto não passa dele); (5) o resto, para a fila.
  const sug = {};
  const livres = () => novas.filter((x) => !sug[x.op]);
  const sentido = (x) => (x.net < 0 ? -1 : 1);
  const usados = new Set();
  const pares = [];
  cands.forEach((c) => novas.forEach((x) => { if (sentido(x) === c.sinal && Math.abs(x.net) === c.valor && mpRelDistDias(x.dia, c.dia) <= MP_REL_JANELA_DIAS) pares.push([c, x]); }));
  pares.forEach(([c, x]) => {
    if (pares.filter((p) => p[0] === c).length === 1 && pares.filter((p) => p[1] === x).length === 1) { sug[x.op] = { tipo: 'titulo', tabela: c.tabela, titulo: c.id }; usados.add(c.id); }
  });
  const outras = (o.outrasEntradas || []).filter((r) => r.conta_id !== conta.id);
  livres().filter((x) => x.net < 0 && x.payout).forEach((x) => {
    const m = outras.filter((r) => C(r.valor) === -x.net && r.data_pagamento && mpRelDistDias(String(r.data_pagamento).slice(0, 10), x.dia) <= MP_REL_JANELA_TRANSF);
    if (m.length === 1 && !Object.values(sug).some((s) => s.outro === m[0].id)) sug[x.op] = { tipo: 'transf', conta: m[0].conta_id, outro: m[0].id };
  });
  cands.filter((c) => !usados.has(c.id)).sort((a, b) => b.valor - a.valor).forEach((c) => {
    const pool = livres().filter((x) => sentido(x) === c.sinal);
    const sol = mpRelSomaExata(pool.map((x) => Math.abs(x.net)), c.valor, MP_REL_MAX_LINHAS);
    if (sol.length === 1) { sol[0].forEach((i) => { sug[pool[i].op] = { tipo: 'titulo', tabela: c.tabela, titulo: c.id }; }); usados.add(c.id); }
  });
  [-1, 1].forEach((s) => {
    const sobrou = cands.filter((c) => c.sinal === s && !usados.has(c.id));
    const pool = livres().filter((x) => sentido(x) === s);
    if (sobrou.length === 1 && pool.length && pool.reduce((a, x) => a + Math.abs(x.net), 0) <= sobrou[0].valor) {
      pool.forEach((x) => { sug[x.op] = { tipo: 'titulo', tabela: sobrou[0].tabela, titulo: sobrou[0].id }; }); usados.add(sobrou[0].id);
    }
  });
  livres().forEach((x) => { sug[x.op] = { tipo: 'fila' }; });
  const temCategoria = (nome) => (o.categorias || []).some((c) => c.nome === nome);
  // as escolhas da tela por cima da sugestão
  const escolhas = o.escolhas || {};
  const plano = [];
  novas.forEach((x) => {
    const d = Object.assign({}, sug[x.op], escolhas[x.op] || {});
    x.sugerido = sug[x.op]; x.destino = d;
    // a fatura do ML: id alfanumérico, pagamento com o saldo → "Taxa de marketplace" (o Lote E separa por código)
    x.ehFatura = !x.numerica && x.payment && x.net < 0;
    const categoria = d.categoria !== undefined ? d.categoria : (x.ehFatura && temCategoria(MP_REL_FATURA_CATEGORIA) ? MP_REL_FATURA_CATEGORIA : null);
    x.categoria = d.tipo === 'transf' || x.net > 0 ? null : categoria;
    const p = { operacao: x.op };
    if (d.tipo === 'titulo') { p.tabela = d.tabela; p.titulo = d.titulo; }
    else if (d.tipo === 'transf') { p.conta_destino = d.conta; if (d.outro) p.titulo_outro_lado = d.outro; }
    else p.fila = true;
    if (x.categoria) p.categoria = x.categoria;
    plano.push(p);
  });
  // os lançamentos substituídos e o que sobra de cada um
  const subst = {};
  novas.forEach((x) => { if (x.destino.tipo === 'titulo' && candPor[x.destino.titulo]) {
    const c = candPor[x.destino.titulo]; const s = subst[c.id] || (subst[c.id] = { c, ops: [], soma: 0 }); s.ops.push(x); s.soma += Math.abs(x.net); } });
  const substituidos = Object.values(subst).map((s) => ({ ...s, sobra: s.c.valor - s.soma })).sort((a, b) => b.c.valor - a.c.valor);

  // o que não casa (a mesma lista do banco)
  const difs = [];
  ordemOps.filter((x) => x.k === 'f2a_dif' || x.k === 'transf_dif').forEach((x) => difs.push({ op: x.op, txt: 'operação ' + x.op + ': no relatório R$ ' + brl(mpRelReal(x.net)) + ', no app R$ ' + brl(mpRelReal(x.k === 'f2a_dif' ? x.appMes : x.trValor)), valor: x.net - (x.k === 'f2a_dif' ? x.appMes : x.trValor) }));
  let somaZero = 0;
  Object.keys(api).forEach((k) => {
    if (ops[k]) return;
    const a = api[k]; const doMes = a.movs.filter((m) => { const d = String(m.dia).slice(0, 10); return d >= abre && d <= fim; });
    if (!doMes.length) return;
    const v = doMes.reduce((s, m) => s + C(m.valor), 0);
    if (v === 0) { somaZero++; return; }
    const tipos = [...new Set(doMes.map((m) => m.tipo))].sort().join('+');
    const d0 = doMes.map((m) => String(m.dia).slice(0, 10)).sort()[0];
    difs.push({ op: k, txt: 'operação ' + k + ' (' + tipos + ' da API, ' + d0.slice(8, 10) + '/' + d0.slice(5, 7) + '): no app R$ ' + brl(mpRelReal(v)) + ', ausente do relatório', valor: -v });
  });
  trs.filter((t) => (t.conta_origem_id === conta.id || t.conta_destino_id === conta.id) && String(t.data).slice(0, 10) >= abre && String(t.data).slice(0, 10) <= fim
    && !ordemOps.some((x) => (x.k === 'transf' || x.k === 'transf_dif') && temOp(t.descricao, x.op)))
    .forEach((t) => difs.push({ txt: 'transferência de ' + fmtDateCurta(t.data) + ' (' + (t.conta_destino_id === conta.id ? 'entrou' : 'saiu') + ' R$ ' + brl(Number(t.valor)) + ', "' + String(t.descricao || '').slice(0, 60) + '"): sem linha no relatório', valor: -valorTr(t) }));
  cands.filter((c) => !novas.some((x) => x.destino.tipo === 'titulo' && x.destino.titulo === c.id))
    .forEach((c) => difs.push({ txt: 'lançamento ' + (c.sinal < 0 ? 'a pagar' : 'a receber') + ' "' + String(c.r.referencia || '').slice(0, 60) + '" (R$ ' + brl(mpRelReal(c.valor)) + ', ' + fmtDateCurta(c.dia) + '): sem linha no relatório e fora do plano', valor: -c.sinal * c.valor }));
  // lançamento à mão que não pode ser substituído (pedido, folha, ligado a fechamento) também não casa
  pags.filter((p) => p.conta_id === conta.id && p.status === 'Pago' && !p.gerado_por && noMes(p) && !candPor[p.id])
    .forEach((p) => difs.push({ txt: 'lançamento a pagar "' + String(p.referencia || '').slice(0, 60) + '" (R$ ' + brl(Number(p.valor)) + '): é de pedido, folha, aporte ou compra — não se substitui', valor: C(p.valor) }));
  recs.filter((r) => r.conta_id === conta.id && (r.status === 'Recebido' || Number(r.valor_recebido) > 0) && !r.gerado_por && noMes(r) && !candPor[r.id])
    .forEach((r) => difs.push({ txt: 'lançamento a receber "' + String(r.referencia || '').slice(0, 60) + '" (R$ ' + brl(Number(r.valor_recebido || r.valor)) + '): é de pedido ou está ligado a fechamento — não se substitui', valor: -C(r.valor_recebido || r.valor) }));

  // a PROVA simulada: o que sai, o que entra, e o saldo do fim do mês pela régua de Contas
  const sai = new Set();
  novas.forEach((x) => { if (x.destino.tipo === 'titulo') sai.add(x.destino.titulo); if (x.destino.tipo === 'transf' && x.destino.outro) sai.add(x.destino.outro); });
  const recs2 = recs.filter((r) => !sai.has(r.id)), pags2 = pags.filter((p) => !sai.has(p.id)), trs2 = trs.slice();
  novas.forEach((x) => {
    const v = Math.abs(x.net) / 100;
    if (x.destino.tipo === 'transf') trs2.push(x.net < 0 ? { conta_origem_id: conta.id, conta_destino_id: x.destino.conta, valor: v, data: x.dia }
      : { conta_origem_id: x.destino.conta, conta_destino_id: conta.id, valor: v, data: x.dia });
    else if (x.net < 0) pags2.push({ conta_id: conta.id, status: 'Pago', valor: v, data_pagamento: x.dia });
    else recs2.push({ conta_id: conta.id, status: 'Recebido', valor: v, valor_recebido: v, data_pagamento: x.dia });
  });
  const movidoDeFora = ordemOps.filter((x) => x.k === 'f2a_dia').reduce((s, x) => s + (x.movidoDeFora || 0), 0);
  const fimDepois = sd(fim, recs2, pags2, trs2) + movidoDeFora;
  const conta1 = (k) => ordemOps.filter((x) => x.k === k).length;
  const resumo = { linhas: L.length, operacoes: ordemOps.length, casamApi: conta1('f2a') + conta1('f2a_dia'), diasCorrigidos: conta1('f2a_dia'),
    casamTransferencias: conta1('transf'), somaZeroRelatorio: conta1('zero'), somaZeroApp: somaZero, novas: novas.length,
    fila: novas.filter((x) => x.destino.tipo === 'fila').length, transferenciasNovas: novas.filter((x) => x.destino.tipo === 'transf').length };
  const fecha = !erros.length && abApp === abBanco && fimDepois === fimBanco && !difs.length;
  return { mes, ini, fim, abre, erros, jaImportado, abBanco, abApp, fimBanco, fimAntes, fimDepois, ops: ordemOps, novas, plano, substituidos, difs,
    resumo, fecha, diferencaFim: fimDepois - fimBanco };
}
function fmtDateCurta(d) { const s = String(d || '').slice(0, 10); return s.slice(8, 10) + '/' + s.slice(5, 7); }
// as linhas para o banco (a forma que o `mp_f2b_importar` lê)
function mpRelLinhasParaBanco(rel) {
  return rel.linhas.map((l) => ({ data: l.data, operacao: l.operacao || null, descricao: l.descricao || null, credito: l.credito, debito: l.debito,
    saldo: l.saldo, metodo: l.metodo || null }));
}
// ── a régua da F2b (funções puras) ── fim ────────────────────────────────────────────────────────────────────────

// ── as gravações (cada uma, uma função do banco numa transação) ──────────────────────────────────────────────────
async function mpRelRpc(nome, args) {
  const neg = window.tmNegadoAqui ? window.tmNegadoAqui('editar') : null;
  if (neg) return { error: { message: neg } };
  const { data, error } = await window.tmSupabase.rpc(nome, args);
  return error ? { error } : { data };
}
async function mpRelSha256(buf) {
  const h = await window.crypto.subtle.digest('SHA-256', buf);
  return Array.from(new Uint8Array(h)).map((b) => b.toString(16).padStart(2, '0')).join('');
}
const MP_REL_DESCR = { payout: 'Pix enviado ou QR pago', payment: 'pagamento', shipping: 'frete/bônus', refund: 'reembolso', mediation: 'reclamação',
  mediation_cancel: 'reclamação desfeita' };
function mpRelDescricao(x) {
  const d = x.descrs.filter((s) => s && s.indexOf('reserve_for_') !== 0);
  return (d.length ? d : x.descrs).map((s) => MP_REL_DESCR[s] || s || '—').join(' + ') + (x.net < 0 ? ' (saída)' : ' (entrada)');
}

// ── a leitura: a base (as contas do MP, os relatórios, as categorias) e os dados de UMA conta ─────────────────────────
function useMpRelBase(reloadKey) {
  const [b, setB] = React.useState({ lendo: true, erro: '' });
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      const sb = window.tmSupabase;
      const [c, l, r, k] = await Promise.all([
        sb.from('contas_bancarias').select('id,nome,tipo,moeda,saldo_inicial,data_saldo_inicial,mp_user_id,empresa_id,situacao').order('nome'),
        sb.from('marketplace_lojas').select('id,nome,plataforma,conta_id,situacao').eq('plataforma', 'Mercado Livre'),
        sb.from('mp_relatorios').select('id,conta_id,loja_id,mes,sha256,arquivo,linhas,saldo_inicial,saldo_final,resumo,importado_em,importado_por_nome').order('mes'),
        sb.from('categorias_financeiras').select('id,nome,uso,situacao').order('nome'),
      ]);
      if (!vivo) return;
      const faltaTabela = r.error && (r.error.code === 'PGRST205' || r.error.code === '42P01');
      const erro = (c.error || l.error || k.error || (r.error && !faltaTabela)) ? ((c.error || l.error || k.error || r.error).message || 'erro de leitura') : '';
      setB({ lendo: false, erro, instalado: !faltaTabela, contas: c.data || [], lojas: l.data || [], relatorios: faltaTabela ? [] : (r.data || []),
        categorias: (k.data || []).filter((x) => x.uso !== 'receita' && x.situacao !== 'Inativa') });
    })();
    return () => { vivo = false; };
  }, [reloadKey]);
  return b;
}
function useMpRelConta(contaId, mes, reloadKey) {
  const [d, setD] = React.useState({ lendo: false, erro: '' });
  React.useEffect(() => {
    if (!contaId) { setD({ lendo: false, erro: '' }); return undefined; }
    let vivo = true;
    setD({ lendo: true, erro: '' });
    (async () => {
      const sb = window.tmSupabase;
      const [mv, tr, rc, pg, di] = await Promise.all([
        lerTodas(() => sb.from('mp_movimentos').select('id,operacao_id,operacao,operacao_ref,tipo,parte,dia,valor,origem,dia_do_relatorio,relatorio_id,descricao,titulo_receber_id,titulo_pagar_id,transferencia_id').eq('conta_id', contaId)),
        lerTodas(() => sb.from('transferencias').select('id,conta_origem_id,conta_destino_id,valor,valor_origem,data,descricao').or('conta_origem_id.eq.' + contaId + ',conta_destino_id.eq.' + contaId)),
        lerTodas(() => sb.from('contas_receber').select('id,referencia,cliente_nome,valor,valor_recebido,status,data_pagamento,natureza,conta_id,pedido_id,devolucao_de_pagar_id,gerado_por').eq('conta_id', contaId)),
        lerTodas(() => sb.from('contas_pagar').select('id,referencia,fornecedor,categoria,valor,valor_pago,status,data_pagamento,conta_id,pedido_id,colaborador_id,aporte_id,pedido_compra_id,vendedor_id,gerado_por').eq('conta_id', contaId)),
        sb.from('emprestimo_distribuicao').select('id,conta_id,valor,data').eq('conta_id', contaId),
      ]);
      let outras = { data: [] }, vinc = { data: [] };
      if (mes) {
        const de = mpRelMaisDias(mes + '-01', -MP_REL_JANELA_TRANSF), ate = mpRelMaisDias(mpRelFimDoMes(mes), MP_REL_JANELA_TRANSF);
        [outras, vinc] = await Promise.all([
          sb.from('contas_receber').select('id,referencia,valor,valor_recebido,status,data_pagamento,conta_id,natureza,gerado_por,pedido_id')
            .eq('natureza', 'transferencia_interna').eq('status', 'Recebido').is('gerado_por', null).is('pedido_id', null).neq('conta_id', contaId)
            .gte('data_pagamento', de).lte('data_pagamento', ate),
          sb.from('mkt_recebimento_vinculos').select('id,conta_receber_id').is('estornado_em', null).not('conta_receber_id', 'is', null),
        ]);
      }
      if (!vivo) return;
      const erro = [mv, tr, rc, pg, di, outras].map((x) => x.error).find(Boolean);
      setD({ lendo: false, erro: erro ? (erro.message || 'erro de leitura') : '', movs: mv.data || [], transfs: tr.data || [], recs: rc.data || [],
        pags: pg.data || [], distrib: di.data || [], outrasEntradas: outras.data || [], vinculosAtivos: (vinc.data || []).map((v) => v.conta_receber_id) });
    })();
    return () => { vivo = false; };
  }, [contaId, mes, reloadKey]);
  return d;
}

// ── a tela ────────────────────────────────────────────────────────────────────────────────────────────────────────
function MpRelDestino({ x, previa, contaId, contas, cands, onEscolher, desligado }) {
  const v = x.destino.tipo === 'titulo' ? 't:' + x.destino.titulo : x.destino.tipo === 'transf' ? 'c:' + x.destino.conta : 'fila';
  const mesmos = cands.filter((c) => (c.sinal < 0) === (x.net < 0));
  return (
    <select className="input mrx-sel" value={v} disabled={desligado} aria-label={'Destino da operação ' + x.op}
      onChange={(e) => {
        const s = e.target.value;
        if (s === 'fila') onEscolher(x.op, { tipo: 'fila' });
        else if (s.indexOf('t:') === 0) { const c = mesmos.find((k) => 't:' + k.id === s); onEscolher(x.op, { tipo: 'titulo', tabela: c.tabela, titulo: c.id }); }
        else if (s.indexOf('c:') === 0) {
          const conta = s.slice(2);
          const outro = (previa.outrasEntradas || []).filter((r) => r.conta_id === conta && Math.round(Number(r.valor) * 100) === -x.net
            && mpRelDistDias(String(r.data_pagamento).slice(0, 10), x.dia) <= MP_REL_JANELA_TRANSF);
          onEscolher(x.op, { tipo: 'transf', conta, outro: x.net < 0 && outro.length === 1 ? outro[0].id : null });
        }
      }}>
      <option value="fila">fila (a classificar)</option>
      {mesmos.map((c) => <option key={c.id} value={'t:' + c.id}>{'substitui "' + String(c.r.referencia || '').slice(0, 48) + '" (R$ ' + brl(c.valor / 100) + ')'}</option>)}
      {contas.filter((c) => c.id !== contaId && (c.moeda || 'BRL') === 'BRL' && c.situacao !== 'Arquivada').map((c) => (
        <option key={c.id} value={'c:' + c.id}>{(x.net < 0 ? 'transferência para ' : 'transferência vinda de ') + c.nome}</option>))}
    </select>
  );
}

function MktExtratoMP() {
  const fm = useMktFiltros();
  const [reloadKey, setReloadKey] = React.useState(0);
  const base = useMpRelBase(reloadKey);
  const contasMP = (base.contas || []).filter((c) => c.mp_user_id != null && c.mp_user_id !== '');
  const [contaId, setContaId] = React.useState('');
  const contaSel = contaId || (contasMP[0] ? contasMP[0].id : '');
  const conta = contasMP.find((c) => c.id === contaSel) || null;
  const [arq, setArq] = React.useState(null);   // { nome, sha, rel, erro }
  const mesArq = arq && arq.rel ? arq.rel.mes : '';
  const d = useMpRelConta(contaSel, mesArq, reloadKey);
  const [escolhas, setEscolhas] = React.useState({});
  const [gravando, setGravando] = React.useState(false);
  const [aviso, setAviso] = React.useState(null);   // { tom, txt }
  const [verOps, setVerOps] = React.useState(false);
  const [classif, setClassif] = React.useState({});   // movimento → categoria/natureza escolhida
  const relsConta = (base.relatorios || []).filter((r) => r.conta_id === contaSel);
  const pronto = !base.lendo && !base.erro && !d.lendo && !d.erro && !!conta;
  const previa = React.useMemo(() => {
    if (!pronto || !arq || !arq.rel) return null;
    const p = mpRelPrevia({ rel: arq.rel, conta, movs: d.movs, transfs: d.transfs, recs: d.recs, pags: d.pags, distrib: d.distrib,
      outrasEntradas: d.outrasEntradas, relatorios: relsConta.filter((r) => !(r.mes === arq.rel.mes)), vinculosAtivos: d.vinculosAtivos,
      categorias: base.categorias, escolhas, saldoDaConta });
    p.outrasEntradas = d.outrasEntradas;
    p.jaImportado = relsConta.find((r) => r.mes === arq.rel.mes) || null;
    return p;
  }, [pronto, arq, d, escolhas, base, conta, relsConta]);   // eslint-disable-line react-hooks/exhaustive-deps
  const cands = React.useMemo(() => {
    if (!previa) return [];
    const C = mpRelCent;
    const noMes = (r) => { const x = String(r.data_pagamento || '').slice(0, 10); return x >= previa.abre && x <= previa.fim; };
    return (d.pags || []).filter((p) => p.status === 'Pago' && !p.gerado_por && !p.pedido_id && !p.colaborador_id && !p.aporte_id && !p.pedido_compra_id && !p.vendedor_id && noMes(p))
      .map((p) => ({ tabela: 'contas_pagar', id: p.id, r: p, valor: C(p.valor), sinal: -1 }))
      .concat((d.recs || []).filter((r) => r.status === 'Recebido' && !r.gerado_por && !r.pedido_id && !r.devolucao_de_pagar_id && !(d.vinculosAtivos || []).includes(r.id) && noMes(r))
        .map((r) => ({ tabela: 'contas_receber', id: r.id, r, valor: C(r.valor_recebido || r.valor), sinal: 1 })));
  }, [previa, d]);

  const lerArquivo = async (ev) => {
    const f = ev.target.files && ev.target.files[0];
    ev.target.value = '';
    setAviso(null); setEscolhas({});
    if (!f) return;
    try {
      const buf = await f.arrayBuffer();
      const sha = await mpRelSha256(buf);
      const rel = mpRelLerCsv(new TextDecoder('utf-8').decode(buf), f.name);
      setArq(rel.erro ? { nome: f.name, sha, erro: rel.erro } : { nome: f.name, sha, rel });
    } catch (e) { setArq({ nome: f.name, erro: 'Não consegui ler o arquivo: ' + (e.message || e) }); }
  };
  const importar = async (substituir) => {
    if (!previa || !arq || !arq.rel) return;
    setGravando(true); setAviso(null);
    const r = await mpRelRpc('mp_f2b_importar', { p_conta: contaSel, p_mes: arq.rel.mes, p_sha256: arq.sha, p_arquivo: arq.nome,
      p_linhas: mpRelLinhasParaBanco(arq.rel), p_plano: previa.plano, p_substituir: !!substituir });
    setGravando(false);
    if (r.error) { setAviso({ tom: 'danger', txt: r.error.message || 'Não importei.' }); return; }
    const x = r.data || {};
    setAviso(x.igual ? { tom: 'info', txt: x.mensagem } : { tom: 'success', txt: 'Importado: ' + mpRelMesRotulo(x.mes) + ' da ' + x.conta + ' — ' + x.linhas + ' linhas, '
      + x.casam_api + ' operações da API casadas (' + x.dias_corrigidos + ' com o dia corrigido), ' + x.novas + ' novas (' + x.na_fila + ' na fila). O saldo do fim do mês é R$ '
      + brl(Number(x.fim_app)) + ' = o do relatório.' });
    setArq(null); setEscolhas({}); setReloadKey((k) => k + 1);
  };
  const desfazer = async (rel) => {
    if (!window.confirm('Desfazer a importação de ' + mpRelMesRotulo(rel.mes) + '? Os lançamentos que ela substituiu voltam, as linhas do extrato saem, e os dias corrigidos voltam ao da API.')) return;
    setGravando(true); setAviso(null);
    const r = await mpRelRpc('mp_f2b_desfazer', { p_relatorio: rel.id });
    setGravando(false);
    if (r.error) { setAviso({ tom: 'danger', txt: r.error.message || 'Não desfiz.' }); return; }
    setAviso({ tom: 'success', txt: 'Desfeito: ' + mpRelMesRotulo(rel.mes) + ' — ' + r.data.titulos_devolvidos + ' lançamento(s) de volta, ' + r.data.movimentos_apagados + ' linha(s) do extrato saíram.' });
    setReloadKey((k) => k + 1);
  };
  const classificar = async (m, tit) => {
    const v = classif[m.id] || {};
    setGravando(true); setAviso(null);
    const r = await mpRelRpc('mp_f2b_classificar', { p_movimento: m.id, p_categoria: m.titulo_pagar_id ? v.categoria || null : null,
      p_natureza: m.titulo_receber_id ? (v.natureza || null) : null, p_contraparte: v.contraparte || null });
    setGravando(false);
    if (r.error) { setAviso({ tom: 'danger', txt: r.error.message || 'Não classifiquei.' }); return; }
    setAviso({ tom: 'success', txt: 'Classificado: "' + String(tit.referencia || '').slice(0, 60) + '".' });
    setReloadKey((k) => k + 1);
  };

  // a fila: as linhas do extrato que nenhum lançamento cobria (categoria "a classificar", ou entrada sem natureza)
  const titPor = {}; (d.recs || []).forEach((r) => { titPor[r.id] = r; }); (d.pags || []).forEach((p) => { titPor[p.id] = p; });
  const fila = (d.movs || []).filter((m) => m.origem === 'relatorio').map((m) => ({ m, t: titPor[m.titulo_pagar_id] || titPor[m.titulo_receber_id] || null }))
    .filter((x) => x.t && ((x.m.titulo_pagar_id && x.t.categoria === MP_REL_FILA_CATEGORIA) || (x.m.titulo_receber_id && !x.t.natureza)))
    .sort((a, b) => String(a.m.dia).localeCompare(String(b.m.dia)));
  const nLinhasRel = (d.movs || []).filter((m) => m.origem === 'relatorio').length;
  const naturezas = (window.NATUREZAS_RECEITA || []).map((n) => n.id);

  return (
    <div className={fm.dentroDoHub ? 'mkt-tela' : 'canvas'}>
      <PageHead title={mktTituloDaTela(fm, 'Marketplace · Recebimentos')}
        sub='O relatório "Liberações" do Mercado Pago, conta por conta e mês a mês: o que a sincronização não vê (saques, Pix enviados, compras, a fatura do ML) entra no caixa, e o saldo do fim do mês tem que ser o do banco.'
        right={<Button variant="ghost" icon="refresh-cw" size="sm" onClick={() => setReloadKey((k) => k + 1)}>Atualizar a tela</Button>} />
      {base.erro && <EstadoDados estado="erro" titulo="Não consegui ler o extrato do MP" mensagem={base.erro + ' — nada foi gravado.'} aoTentar={() => setReloadKey((k) => k + 1)} />}
      {!base.lendo && !base.erro && base.instalado === false && <EstadoDados estado="vazio" titulo="O banco ainda não tem a importação do extrato" mensagem="O SUPABASE_mp-f2b.sql ainda não rodou: a tela lê o arquivo e mostra a prévia, mas não grava." compacto />}
      {!base.lendo && !base.erro && !contasMP.length && <EstadoDados estado="vazio" titulo="Nenhuma conta do Mercado Pago com o número" mensagem="O extrato entra só numa conta com a identidade do Mercado Pago (o número da conta)." compacto />}
      {contasMP.length > 0 && (
        <div className="mla-barra"><div className="mla-chips" role="group" aria-label="Conta do Mercado Pago">
          {contasMP.map((c) => <Chip key={c.id} active={c.id === contaSel} onClick={() => { setContaId(c.id); setArq(null); setEscolhas({}); setAviso(null); }}>{c.nome}</Chip>)}
        </div></div>)}
      {aviso && <div className={'mkt-nota mrx-aviso mrx-' + aviso.tom} role={aviso.tom === 'danger' ? 'alert' : 'status'}><Icon name={aviso.tom === 'danger' ? 'alert-circle' : aviso.tom === 'success' ? 'check-circle' : 'info'} size={14} /><span>{aviso.txt}</span></div>}
      {conta && (
        <div className="td-caixa mrx-meses">
          <div className="mrc-ponte-tit"><Icon name="file-text" size={14} /> <b>Meses importados — {conta.nome}</b> <span className="muted">· o livro começa em {fmtDate(conta.data_saldo_inicial)} · número do Mercado Pago {conta.mp_user_id}</span></div>
          {relsConta.length === 0 ? <div className="muted mrx-vazio">Nenhum mês importado ainda. Comece pelo mês do saldo inicial ({mpRelMesRotulo(String(conta.data_saldo_inicial || '').slice(0, 7) || '2026-08')}).</div> : (
            <table className="tbl"><thead><tr><th>Mês</th><th>Arquivo</th><th className="td-num">Linhas</th><th className="td-num">Saldo no começo</th><th className="td-num">Saldo no fim (= o app)</th><th>Importado</th><th className="td-num">Novas · fila</th><th /></tr></thead>
              <tbody>{relsConta.map((r, i) => (
                <tr key={r.id}><td>{mpRelMesRotulo(r.mes)}</td><td className="mlv-sit-det">{r.arquivo || '—'}<div>sha256 {String(r.sha256).slice(0, 12)}…</div></td>
                  <td className="td-num">{r.linhas}</td><td className="td-num">R$ {brl(Number(r.saldo_inicial))}</td><td className="td-num">R$ {brl(Number(r.saldo_final))} <Badge tone="success">fecha</Badge></td>
                  <td className="mlv-sit-det">{fmtDatetime(r.importado_em)}{r.importado_por_nome ? ' · ' + r.importado_por_nome : ''}</td>
                  <td className="td-num">{(r.resumo && r.resumo.novas) || 0} · {(r.resumo && r.resumo.na_fila) || 0}</td>
                  <td>{i === relsConta.length - 1 && <Button variant="ghost" size="sm" icon="rotate-ccw" pede="editar" disabled={gravando} onClick={() => desfazer(r)}>Desfazer</Button>}</td></tr>))}</tbody></table>)}
          <div className="mrx-importar">
            <label className={'btn secondary sm mrx-escolher' + (gravando ? ' mrx-desligado' : '')}>
              <Icon name="upload" size={14} /> Escolher o relatório "Liberações" (CSV)
              <input type="file" accept=".csv,text/csv" className="mrx-arquivo" onChange={lerArquivo} disabled={gravando} aria-label='Relatório "Liberações" do Mercado Pago, em CSV' />
            </label>
            <span className="mlv-sit-det">Um arquivo por conta e mês. Nada é gravado antes de você confirmar a prévia.</span>
          </div>
        </div>)}
      {arq && arq.erro && <div className="mkt-nota mrx-aviso mrx-danger" role="alert"><Icon name="alert-circle" size={14} /><span><b>{arq.nome}</b>: {arq.erro}</span></div>}
      {arq && arq.rel && !pronto && <EstadoDados estado={d.erro ? 'erro' : 'carregando'} titulo={d.erro ? 'Não consegui ler a conta' : 'Lendo a conta para a prévia…'} mensagem={d.erro || ''} compacto />}
      {previa && (
        <div className="td-caixa mrx-previa">
          <div className="mrc-ponte-tit"><Icon name="search" size={14} /> <b>Prévia — {mpRelMesRotulo(previa.mes)} · {conta.nome}</b> <span className="muted">· {arq.nome} · sha256 {arq.sha.slice(0, 12)}… · {previa.resumo.linhas} linhas</span></div>
          {previa.jaImportado && previa.jaImportado.sha256 === arq.sha && <div className="mkt-nota"><Icon name="info" size={14} /><span>Este arquivo já foi importado, igual — nada a fazer.</span></div>}
          {previa.jaImportado && previa.jaImportado.sha256 !== arq.sha && <div className="mkt-nota mrc-pendencia"><Icon name="alert-circle" size={14} /><span>{mpRelMesRotulo(previa.mes)} já foi importado com OUTRO arquivo. "Substituir" desfaz o de antes e importa este, numa vez só.</span></div>}
          {previa.erros.map((e, i) => <div key={i} className="mkt-nota mrx-aviso mrx-danger" role="alert"><Icon name="alert-circle" size={14} /><span>{e}</span></div>)}
          <table className="tbl mrx-prova"><tbody>
            <tr><td>Abertura ({fmtDate(previa.abre)})</td><td className="td-num">banco R$ {brl(previa.abBanco / 100)}</td><td className="td-num">app R$ {brl(previa.abApp / 100)}</td>
              <td>{previa.abApp === previa.abBanco ? <Badge tone="success">fecha</Badge> : <Badge tone="danger">não fecha</Badge>}</td></tr>
            <tr><td>Fim ({fmtDate(previa.fim)})</td><td className="td-num">banco R$ {brl(previa.fimBanco / 100)}</td>
              <td className="td-num">app hoje R$ {brl(previa.fimAntes / 100)} → depois R$ {brl(previa.fimDepois / 100)}</td>
              <td>{previa.fimDepois === previa.fimBanco ? <Badge tone="success">fecha</Badge> : <Badge tone="danger">diferença R$ {brl(previa.diferencaFim / 100)}</Badge>}</td></tr>
          </tbody></table>
          <div className="mrx-contas">
            <span><b>{previa.resumo.casamApi}</b> operações da API casam ({previa.resumo.diasCorrigidos} com o dia do relatório)</span>
            <span><b>{previa.resumo.casamTransferencias}</b> casam com transferências (caixinhas)</span>
            {previa.resumo.somaZeroApp > 0 && <span><b>{previa.resumo.somaZeroApp}</b> do app somam zero e o relatório não mostra (iguais)</span>}
            <span><b>{previa.resumo.somaZeroRelatorio}</b> só reserva e liberação (soma zero)</span>
            <span><b>{previa.resumo.novas}</b> novas · {previa.resumo.fila} na fila · {previa.resumo.transferenciasNovas} transferência(s)</span>
          </div>
          {previa.difs.length > 0 && (
            <div className="mkt-nota mrx-aviso mrx-danger" role="alert"><Icon name="alert-circle" size={14} />
              <span><b>O que não casa — a importação não pode ser confirmada até isto ser entendido:</b><ul className="mrx-lista">{previa.difs.map((x, i) => <li key={i}>{x.txt}</li>)}</ul></span></div>)}
          {previa.ops.filter((x) => x.k === 'f2a_dia').length > 0 && (
            <div className="mlv-sit-det mrx-bloco">O dia do relatório corrige o da API (a sincronização não volta a mexer): {previa.ops.filter((x) => x.k === 'f2a_dia').map((x) => x.op + ' ' + fmtDateCurta(x.appDia) + ' → ' + fmtDateCurta(x.dia)).join(' · ')}.</div>)}
          {previa.substituidos.length > 0 && (
            <div className="mrx-bloco"><b>Lançamentos do app que as linhas substituem</b> (as linhas herdam a categoria e a natureza deles)
              <table className="tbl"><thead><tr><th>Lançamento</th><th className="td-num">Valor</th><th className="td-num">Linhas</th><th className="td-num">Soma das linhas</th><th>O que sobra</th></tr></thead>
                <tbody>{previa.substituidos.map((s) => (
                  <tr key={s.c.id}><td>{s.c.r.referencia}<div className="mlv-sit-det">{s.c.tabela === 'contas_pagar' ? (s.c.r.categoria || 'sem categoria') : (s.c.r.natureza || 'sem natureza')} · {fmtDate(s.c.dia)}</div></td>
                    <td className="td-num">R$ {brl(s.c.valor / 100)}</td><td className="td-num">{s.ops.length}</td><td className="td-num">R$ {brl(s.soma / 100)}</td>
                    <td className="mlv-sit-det">{s.sobra === 0 ? 'nada — fecha ao centavo' : s.sobra > 0 ? 'R$ ' + brl(s.sobra / 100) + ' deste lançamento não têm linha no extrato e saem' : 'as linhas passam do lançamento em R$ ' + brl(-s.sobra / 100)}</td></tr>))}</tbody></table></div>)}
          {previa.novas.length > 0 && (
            <div className="mrx-bloco"><b>As {previa.novas.length} operações novas</b> — o destino de cada uma (a sugestão vem pela soma exata; mude onde precisar)
              <table className="tbl"><thead><tr><th>Dia</th><th>Operação</th><th>O que é</th><th className="td-num">Valor</th><th>Destino</th></tr></thead>
                <tbody>{previa.novas.map((x) => (
                  <tr key={x.op}><td>{fmtDateCurta(x.dia)}</td><td className="ref-mono">{x.op}</td>
                    <td className="mlv-sit-det">{mpRelDescricao(x)}{x.categoria ? ' · categoria: ' + x.categoria + (x.ehFatura ? ' (fatura do ML)' : '') : ''}</td>
                    <td className="td-num">{x.net < 0 ? '−' : ''}R$ {brl(Math.abs(x.net) / 100)}</td>
                    <td><MpRelDestino x={x} previa={previa} contaId={contaSel} contas={base.contas} cands={cands} desligado={gravando}
                      onEscolher={(op, e) => setEscolhas((s) => Object.assign({}, s, { [op]: e }))} /></td></tr>))}</tbody></table></div>)}
          <button type="button" className="fc-linkbtn" aria-expanded={verOps} onClick={() => setVerOps((v) => !v)}>{verOps ? 'Esconder' : 'Ver'} todas as {previa.ops.length} operações do relatório</button>
          {verOps && (
            <div className="mrx-ops"><table className="tbl"><thead><tr><th>Dia</th><th>Operação</th><th>O que é</th><th className="td-num">Valor</th><th>No app</th></tr></thead>
              <tbody>{previa.ops.map((x) => (
                <tr key={x.op}><td>{fmtDateCurta(x.dia)}</td><td className="ref-mono">{x.op}</td><td className="mlv-sit-det">{mpRelDescricao(x)}</td>
                  <td className="td-num">R$ {brl(x.net / 100)}</td>
                  <td className="mlv-sit-det">{({ f2a: 'casa com a API', f2a_dia: 'casa com a API (dia corrigido)', f2a_dif: 'não casa com a API', transf: 'casa com a transferência', transf_dif: 'não casa com a transferência', zero: 'soma zero', novo: 'nova' })[x.k]}</td></tr>))}</tbody></table></div>)}
          <div className="mrx-acoes">
            {!(previa.jaImportado && previa.jaImportado.sha256 === arq.sha) && (
              <Button icon="check" pede="editar" disabled={gravando || !previa.fecha || base.instalado === false}
                title={!previa.fecha ? 'A prova não fecha: nada se grava até o que não casa ser entendido.' : ''}
                onClick={() => importar(!!previa.jaImportado)}>{previa.jaImportado ? 'Substituir ' : 'Importar '}{mpRelMesRotulo(previa.mes)}</Button>)}
            <Button variant="ghost" onClick={() => { setArq(null); setEscolhas({}); }}>Cancelar</Button>
            {!previa.fecha && <span className="mlv-sit-det">A prova não fecha: o botão fica desligado. Nada foi gravado.</span>}
          </div>
        </div>)}
      {conta && pronto && (
        <div className="td-caixa mrx-fila">
          <div className="mrc-ponte-tit"><Icon name="list-checks" size={14} /> <b>A classificar — linhas do extrato sem lançamento que as cobrisse</b> <span className="muted">· {fila.length} de {nLinhasRel} linha(s) do extrato no caixa desta conta</span></div>
          {fila.length === 0 ? <div className="muted mrx-vazio">Nada a classificar.</div> : (
            <table className="tbl"><thead><tr><th>Dia</th><th>Linha</th><th className="td-num">Valor</th><th>Classificação</th><th /></tr></thead>
              <tbody>{fila.map(({ m, t }) => {
                const v = classif[m.id] || {};
                const set = (k, val) => setClassif((s) => Object.assign({}, s, { [m.id]: Object.assign({}, s[m.id] || {}, { [k]: val }) }));
                return (
                  <tr key={m.id}><td>{fmtDateCurta(m.dia)}</td><td>{t.referencia}<div className="mlv-sit-det">operação {m.operacao}</div></td>
                    <td className="td-num">{Number(m.valor) < 0 ? '−' : ''}R$ {brl(Math.abs(Number(m.valor)))}</td>
                    <td>{m.titulo_pagar_id
                      ? <select className="input mrx-sel" value={v.categoria || ''} onChange={(e) => set('categoria', e.target.value)} aria-label={'Categoria da operação ' + m.operacao}>
                          <option value="">escolha a categoria…</option>{(base.categorias || []).map((c) => <option key={c.id} value={c.nome}>{c.nome}</option>)}</select>
                      : <select className="input mrx-sel" value={v.natureza || ''} onChange={(e) => set('natureza', e.target.value)} aria-label={'Natureza da operação ' + m.operacao}>
                          <option value="">escolha a natureza…</option>{naturezas.map((n) => <option key={n} value={n}>{window.naturezaLabel ? window.naturezaLabel(n) : n}</option>)}</select>}
                      <input className="input mrx-contra" placeholder={m.titulo_pagar_id ? 'para quem (opcional)' : 'de quem (opcional)'} value={v.contraparte || ''}
                        onChange={(e) => set('contraparte', e.target.value)} aria-label={'Contraparte da operação ' + m.operacao} /></td>
                    <td><Button size="sm" pede="editar" disabled={gravando || (m.titulo_pagar_id ? !v.categoria : !v.natureza)} onClick={() => classificar(m, t)}>Classificar</Button></td></tr>);
              })}</tbody></table>)}
        </div>)}
    </div>
  );
}

Object.assign(window, {
  MktExtratoMP, MpRelDestino, useMpRelBase, useMpRelConta, mpRelRpc, mpRelSha256, mpRelDescricao, MP_REL_DESCR,
  MP_REL_COLUNAS, MP_REL_FILA_CATEGORIA, MP_REL_FATURA_CATEGORIA, MP_REL_JANELA_DIAS, MP_REL_JANELA_TRANSF, MP_REL_MAX_LINHAS,
  mpRelCent, mpRelReal, mpRelNum, mpRelDia, mpRelFimDoMes, mpRelMesAnterior, mpRelMaisDias, mpRelDistDias, mpRelMesRotulo, mpRelSepara,
  mpRelLerCsv, mpRelSomaExata, mpRelPrevia, fmtDateCurta, mpRelLinhasParaBanco,
});
