/* global React, Icon, Button, PageHead, brl, fmtDate, lerTodas,
          fromDbContaRec, fromDbContaPag, fromDbConta, fromDbTransferencia, fromDbDistribuicao,
          finEntrouNoCaixa, finValorQueEntrou, saldoDaConta, corteDaConta, vincularContaEmLote, tmNegadoAqui */

// ============================================================
// Configurações › Financeiro › Classificação pendente › Lançamentos sem conta
// ============================================================
// Título que movimentou dinheiro e não diz EM QUE CONTA: o valor entrou no caixa e não está no saldo
// de conta nenhuma. Em 21/09/2026 eram 74 títulos em 2026, +R$ 59.365,27 no balde "sem conta" do
// Extrato — quase tudo venda por WhatsApp. A fila leva cada um para a conta dele.
//
// O que ela NÃO faz, de propósito:
//  · não sugere a conta — palpite gravado vira fato sem ninguém perceber (a mesma regra da fila de
//    canal). Se um critério seguro aparecer (forma de pagamento, contraparte), ele é PROPOSTO e ela
//    decide; nunca pré-marcado;
//  · não mexe em resultado, variação de caixa nem DRE: é o mesmo dinheiro, agora com endereço. Muda o
//    saldo da conta escolhida e tira o título de "Sem conta" — e a confirmação diz exatamente isso,
//    calculado pelas MESMAS funções da tela de Contas (`saldoDaConta`, `corteDaConta`), antes e depois.
//
// DUAS listas, porque são dois fatos diferentes:
//  · NO CAIXA — o título já movimentou dinheiro (entrou: `finEntrouNoCaixa`, com data; saiu: Pago, com
//    data). É o que soma na linha "Sem conta" de Contas bancárias (`saldoSemConta`) — e, sem as ⇄, o
//    balde "sem conta" do Extrato. Vincular muda o saldo da conta;
//  · FORA DO CAIXA — ainda em aberto, ou pago sem data de pagamento. Vincular não muda saldo nenhum
//    hoje: vale para quando o dinheiro se mover (ou a data for preenchida).

// ─── Funções puras (a PROVA extrai estas) ────────────────────────────────────

function scData(r) {
  const dp = String(r.data_pagamento || r.dataPagamento || '').slice(0, 10);
  return /^\d{4}-\d{2}-\d{2}$/.test(dp) ? dp : null;
}

// Os títulos sem conta, separados em no caixa × fora do caixa. `valor` é o que moveu o caixa (o
// recebido, no parcial), `sinal` +1 entrada / −1 saída.
function scItens(recRows, pagRows) {
  const noCaixa = [], foraDoCaixa = [];
  (recRows || []).forEach(r => {
    if (!r || r.conta_id) return;
    const dp = scData(r);
    const entrou = finEntrouNoCaixa(r) && !!dp;
    const item = { key: 'contas_receber:' + r.id, id: r.id, tabela: 'contas_receber', sinal: 1,
      data: dp || String(r.vencimento || '').slice(0, 10) || null,
      desc: r.referencia || r.ref || 'Recebimento', contraparte: r.cliente_nome || r.cli || '',
      forma: r.forma_pagamento || '', status: r.status || 'Em aberto',
      interna: r.natureza === 'transferencia_interna',
      semData: !dp && finEntrouNoCaixa(r),   // o dinheiro entrou, mas sem data não entra em saldo
      valor: entrou ? finValorQueEntrou(r) : (Number(r.valor) || 0) };
    (entrou ? noCaixa : foraDoCaixa).push(item);
  });
  (pagRows || []).forEach(p => {
    if (!p || p.conta_id) return;
    const dp = scData(p);
    const saiu = p.status === 'Pago' && !!dp;
    const item = { key: 'contas_pagar:' + p.id, id: p.id, tabela: 'contas_pagar', sinal: -1,
      data: dp || String(p.vencimento || '').slice(0, 10) || null,
      desc: p.referencia || p.ref || p.fornecedor || 'Pagamento', contraparte: p.fornecedor || p.forn || '',
      forma: p.forma_pagamento || '', status: p.status || 'Em aberto', interna: false,
      semData: !dp && p.status === 'Pago',
      valor: Number(p.valor) || 0 };
    (saiu ? noCaixa : foraDoCaixa).push(item);
  });
  return { noCaixa, foraDoCaixa };
}

// Soma de um conjunto: entradas, saídas e o líquido — o que a barra de seleção mostra.
function scResumo(itens) {
  const r = { n: 0, entradas: 0, saidas: 0, liquido: 0 };
  (itens || []).forEach(it => {
    r.n += 1;
    if (it.sinal > 0) r.entradas += it.valor; else r.saidas += it.valor;
    r.liquido += it.sinal * it.valor;
  });
  return r;
}

// Ordena por |valor| (maiores primeiro, que é como ela vai atacar) ou por data (mais recente primeiro).
function scOrdena(itens, por) {
  const l = (itens || []).slice();
  if (por === 'data') l.sort((a, b) => String(b.data || '').localeCompare(String(a.data || '')));
  else l.sort((a, b) => b.valor - a.valor);
  return l;
}

// O EFEITO de vincular `alvos` à `conta`, medido pelas funções da tela de Contas sobre o antes e o
// depois — nunca uma conta à parte. `antesDaAncora` = os que a âncora da conta deixa fora do saldo.
function scEfeito(alvos, conta, recRows, pagRows, transfRows, distribRows) {
  const ids = new Set((alvos || []).map(a => a.key));
  const comConta = (tabela) => (r) => (ids.has(tabela + ':' + r.id) ? Object.assign({}, r, { conta_id: conta.id }) : r);
  const rec2 = (recRows || []).map(comConta('contas_receber'));
  const pag2 = (pagRows || []).map(comConta('contas_pagar'));
  const antes = saldoDaConta(conta, recRows, pagRows, transfRows, distribRows);
  const depois = saldoDaConta(conta, rec2, pag2, transfRows, distribRows);
  const ancora = conta.data_saldo_inicial ? String(conta.data_saldo_inicial).slice(0, 10) : null;
  const fora = (alvos || []).filter(a => ancora && a.data && a.data < ancora);
  return {
    n: (alvos || []).length,
    saldoAntes: antes, saldoDepois: depois, delta: depois - antes,
    corteAntes: corteDaConta(conta, recRows, pagRows, transfRows, distribRows),
    corteDepois: corteDaConta(conta, rec2, pag2, transfRows, distribRows),
    ancora, antesDaAncora: scResumo(fora),
  };
}

const scMoeda = (n) => (n < 0 ? '− ' : '+ ') + 'R$ ' + brl(Math.abs(n));

// ─── A tela ───────────────────────────────────────────────────────────────────

// A leitura é da PRÓPRIA fila, não dos hooks de lista: eles engolem o erro e devolvem [], e aqui uma
// lista vazia diz "a linha Sem conta está zerada" — mentira, se a leitura falhou. `null` = ainda não
// li; erro = digo que não consegui. Mesma paginação (`lerTodas`) e mesmos mappers da tela de Contas.
async function scLer() {
  const sb = window.tmSupabase;
  if (!sb) return { erro: 'sem conexão com o banco' };
  const ler = (t) => lerTodas(() => sb.from(t).select('*'));
  const [cr, cp, cb, tr, di] = await Promise.all(['contas_receber', 'contas_pagar', 'contas_bancarias', 'transferencias', 'emprestimo_distribuicao'].map(ler));
  const falhou = [cr, cp, cb, tr, di].map(r => r && r.error).find(Boolean);
  if (falhou) return { erro: String(falhou.message || falhou) };
  return { dados: { rec: (cr.data || []).map(fromDbContaRec), pag: (cp.data || []).map(fromDbContaPag),
    contas: (cb.data || []).map(fromDbConta), transf: (tr.data || []).map(fromDbTransferencia),
    distrib: (di.data || []).map(fromDbDistribuicao) } };
}

function FilaSemConta({ onNav }) {
  const [dados, setDados] = React.useState(null);
  const [erroLeitura, setErroLeitura] = React.useState('');
  const [recarga, setRecarga] = React.useState(0);
  React.useEffect(() => {
    let vivo = true;
    scLer().then(r => { if (!vivo) return; if (r.erro) { setErroLeitura(r.erro); setDados(null); } else { setErroLeitura(''); setDados(r.dados); } })
      .catch(e => { if (vivo) { setErroLeitura(String((e && e.message) || e)); setDados(null); } });
    return () => { vivo = false; };
  }, [recarga]);
  const [aba, setAba] = React.useState('caixa');
  const [tipo, setTipo] = React.useState('todos');
  const [busca, setBusca] = React.useState('');
  const [ordem, setOrdem] = React.useState('valor');
  const [sel, setSel] = React.useState(() => new Set());
  const [contaId, setContaId] = React.useState('');
  const [confirmando, setConfirmando] = React.useState(false);
  const [gravando, setGravando] = React.useState(false);
  const [relato, setRelato] = React.useState(null);

  const carregando = !dados && !erroLeitura;
  const recRows = dados ? dados.rec : [], pagRows = dados ? dados.pag : [];
  const transfRows = dados ? dados.transf : [], distribRows = dados ? dados.distrib : [];
  const grupos = React.useMemo(() => scItens(recRows, pagRows), [recRows, pagRows]);
  const base = aba === 'caixa' ? grupos.noCaixa : grupos.foraDoCaixa;
  const q = busca.trim().toLowerCase();
  const visiveis = scOrdena(base.filter(it =>
    (tipo === 'todos' || (tipo === 'entradas' ? it.sinal > 0 : it.sinal < 0)) &&
    (!q || [it.desc, it.contraparte, it.forma].join(' ').toLowerCase().includes(q))), ordem);
  const marcados = base.filter(it => sel.has(it.key));
  const somaSel = scResumo(marcados);
  const somaAba = scResumo(base);
  // Só conta em REAL e ativa: título é em real, e conta em outra moeda somaria sem câmbio.
  const contas = (dados ? dados.contas : []).filter(c => (c.moeda || 'BRL') === 'BRL' && c.situacao !== 'Arquivada');
  const conta = contas.find(c => c.id === contaId) || null;
  const efeito = React.useMemo(() => (conta && marcados.length
    ? scEfeito(marcados, conta, recRows, pagRows, transfRows, distribRows) : null),
  // eslint-disable-next-line react-hooks/exhaustive-deps
  [conta, sel, dados]);
  const negado = window.tmNegadoAqui ? window.tmNegadoAqui('editar') : null;

  const trocaAba = (a) => { setAba(a); setSel(new Set()); setConfirmando(false); };
  const toggle = (key) => { setConfirmando(false); setSel(prev => { const n = new Set(prev); n.has(key) ? n.delete(key) : n.add(key); return n; }); };
  const todosVisiveis = visiveis.length > 0 && visiveis.every(it => sel.has(it.key));
  const toggleVisiveis = () => { setConfirmando(false); setSel(prev => {
    const n = new Set(prev);
    if (todosVisiveis) visiveis.forEach(it => n.delete(it.key)); else visiveis.forEach(it => n.add(it.key));
    return n; }); };

  const vincular = async () => {
    // escrita que chega aqui sem passar pelo botão também pergunta (a regra do Enter)
    if (negado || !conta || !marcados.length || gravando) return;
    setGravando(true); setRelato(null);
    const porTabela = { contas_receber: [], contas_pagar: [] };
    marcados.forEach(it => porTabela[it.tabela].push(it.id));
    const soma = { gravados: [], falhas: [], jaTinham: [], recusadosSemErro: [] };
    for (const tabela of Object.keys(porTabela)) {
      if (!porTabela[tabela].length) continue;
      const r = await vincularContaEmLote(tabela, porTabela[tabela], conta.id);
      Object.keys(soma).forEach(k => { soma[k] = soma[k].concat(r[k]); });
    }
    const nome = {}; marcados.forEach(it => { nome[it.id] = it.desc; });
    setRelato({ conta: conta.nome, pedidos: marcados.length, ...soma, nome });
    setSel(prev => { const n = new Set(prev); marcados.forEach(it => { if (soma.gravados.indexOf(it.id) >= 0 || soma.jaTinham.indexOf(it.id) >= 0) n.delete(it.key); }); return n; });
    setConfirmando(false); setGravando(false);
    setRecarga(k => k + 1);
  };

  const th = (rotulo, chave) => (
    <th className={chave === 'valor' ? 'num right' : undefined} style={{ cursor: 'pointer', whiteSpace: 'nowrap' }}
      onClick={() => setOrdem(chave)} title={'Ordenar por ' + rotulo.toLowerCase()}>
      {rotulo}{ordem === chave ? ' ↓' : ''}
    </th>);

  return (
    <div className="canvas">
      <PageHead title="Lançamentos sem conta"
        sub="entradas e saídas sem a conta em que o dinheiro caiu — escolha a conta; o valor entra no saldo dela e sai de “Sem conta”" />

      <div className="bal-aviso info" style={{ marginBottom: 16 }}>
        <Icon name="info" size={15} />
        <div>
          <b>O que muda e o que não muda.</b> Vincular muda o <b>saldo da conta escolhida</b> e tira o título da linha
          “Sem conta” (em Contas bancárias e no Extrato). <b>Não muda</b> o resultado, a variação de caixa nem o DRE — é o
          mesmo dinheiro, agora com endereço. A fila <b>não sugere</b> conta: você escolhe cada grupo.
        </div>
      </div>

      <div style={{ display: 'flex', gap: 8, marginBottom: 12, flexWrap: 'wrap' }}>
        {[['caixa', 'No caixa', grupos.noCaixa], ['fora', 'Fora do caixa', grupos.foraDoCaixa]].map(([id, rot, lst]) => {
          const r = scResumo(lst);
          return (
            <button key={id} type="button" className={'chip' + (aba === id ? ' active' : '')} onClick={() => trocaAba(id)}>
              {rot} · {carregando ? '…' : r.n}{!carregando && id === 'caixa' ? ' · ' + scMoeda(r.liquido) : ''}
            </button>);
        })}
      </div>
      <div className="muted" style={{ fontSize: 12, marginBottom: 12, lineHeight: 1.55 }}>
        {aba === 'caixa'
          ? <>Títulos que <b>já movimentaram dinheiro</b> (recebidos, inclusive em parte, e pagos, com data). A soma é a linha
             “Sem conta” de Contas bancárias; sem as transferências internas (⇄), é o balde “sem conta” do Extrato — que
             mostra só o período escolhido lá.</>
          : <>Títulos <b>em aberto</b>, ou pagos/recebidos <b>sem data de pagamento</b>. Vincular não muda saldo nenhum hoje:
             vale para quando o dinheiro se mover (ou a data for preenchida). Os sem data não entram em saldo até ganhar data.</>}
      </div>

      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 10, flexWrap: 'wrap' }}>
        <select className="input" style={{ maxWidth: 160 }} value={tipo} onChange={e => setTipo(e.target.value)}>
          <option value="todos">Entradas e saídas</option>
          <option value="entradas">Só entradas</option>
          <option value="saidas">Só saídas</option>
        </select>
        <input className="input" style={{ maxWidth: 280 }} placeholder="Buscar descrição, contraparte, forma…"
          value={busca} onChange={e => setBusca(e.target.value)} />
        <span className="muted" style={{ fontSize: 12 }}>{visiveis.length} de {base.length} na lista</span>
      </div>

      {sel.size > 0 && (
        <div className="bal-aviso" style={{ marginBottom: 10, display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
          <Icon name="check-square" size={15} />
          <span style={{ fontSize: 12.5 }}>
            <b>{somaSel.n} selecionado{somaSel.n !== 1 ? 's' : ''}</b> · entradas R$ {brl(somaSel.entradas)} · saídas R$ {brl(somaSel.saidas)} ·
            líquido <b>{scMoeda(somaSel.liquido)}</b>
          </span>
          <span style={{ flex: 1 }} />
          <select className="input" style={{ maxWidth: 260 }} value={contaId}
            onChange={e => { setContaId(e.target.value); setConfirmando(false); }}>
            <option value="">Vincular à conta… (em real)</option>
            {contas.map(c => <option key={c.id} value={c.id}>{c.nome}</option>)}
          </select>
          <Button variant="primary" size="sm" icon="link" pede="editar" disabled={!conta || gravando}
            onClick={() => setConfirmando(true)}>Ver o efeito</Button>
        </div>
      )}

      {confirmando && efeito && (
        <div className="bal-aviso warning" style={{ marginBottom: 12 }}>
          <Icon name="alert-triangle" size={15} />
          <div style={{ flex: 1 }}>
            <b>Vincular {efeito.n} lançamento{efeito.n !== 1 ? 's' : ''} à conta {conta.nome}:</b>
            <div style={{ marginTop: 4 }}>
              {Math.abs(efeito.delta) < 0.005
                ? <>· o saldo de {conta.nome} <b>não muda</b>{aba === 'fora' ? ' — nenhum deles movimentou dinheiro ainda (ou falta a data de pagamento)' : ''};</>
                : <>· o saldo de {conta.nome} vai de R$ {brl(efeito.saldoAntes)} para <b>R$ {brl(efeito.saldoDepois)}</b> ({scMoeda(efeito.delta)});</>}
            </div>
            {efeito.antesDaAncora.n > 0 && (
              <div style={{ marginTop: 2 }}>
                · {efeito.antesDaAncora.n} deles {efeito.antesDaAncora.n === 1 ? 'é' : 'são'} de antes de {fmtDate(efeito.ancora)} — o
                saldo inicial de {conta.nome} — e <b>não entra{efeito.antesDaAncora.n === 1 ? '' : 'm'} no saldo dela</b> ({scMoeda(efeito.antesDaAncora.liquido)});
              </div>)}
            {efeito.corteDepois !== efeito.corteAntes && efeito.corteDepois && (
              <div style={{ marginTop: 2 }}>· o último lançamento de {conta.nome} passa a ser {fmtDate(efeito.corteDepois)};</div>)}
            <div style={{ marginTop: 2 }}>· saem de “Sem conta”; o resultado, a variação de caixa e o DRE <b>não mudam</b>.</div>
            <div style={{ display: 'flex', gap: 8, marginTop: 10 }}>
              <Button variant="primary" size="sm" icon="check" pede="editar" disabled={gravando} onClick={vincular}>
                {gravando ? 'Vinculando…' : 'Confirmar'}</Button>
              <Button variant="secondary" size="sm" disabled={gravando} onClick={() => setConfirmando(false)}>Voltar</Button>
            </div>
          </div>
        </div>
      )}

      {relato && (
        <div className={'bal-aviso ' + (relato.falhas.length || relato.recusadosSemErro.length ? 'danger' : 'success')} style={{ marginBottom: 12 }}>
          <Icon name={relato.falhas.length || relato.recusadosSemErro.length ? 'alert-circle' : 'check-circle'} size={15} />
          <div>
            <b>Vinculados {relato.gravados.length} de {relato.pedidos}</b> à conta {relato.conta}.
            {relato.jaTinham.length > 0 && <div style={{ marginTop: 2 }}>{relato.jaTinham.length} já tinha{relato.jaTinham.length === 1 ? '' : 'm'} conta (alguém vinculou antes) — não foram tocados.</div>}
            {relato.falhas.map(f => <div key={f.id} style={{ marginTop: 2 }}>✗ {relato.nome[f.id] || f.id}: {f.erro}</div>)}
            {relato.recusadosSemErro.length > 0 && <div style={{ marginTop: 2 }}>✗ {relato.recusadosSemErro.length} recusado{relato.recusadosSemErro.length === 1 ? '' : 's'} pelo banco sem mensagem (continuam sem conta): {relato.recusadosSemErro.map(id => relato.nome[id] || id).join(', ')}.</div>}
          </div>
        </div>
      )}

      {erroLeitura ? (
        <div className="bal-aviso danger"><Icon name="alert-circle" size={15} />
          <div><b>Não consegui ler os títulos</b> ({erroLeitura}). A fila não diz que está vazia sem ter lido —
            recarregue a página; se continuar, o erro acima é o que o banco respondeu.</div></div>
      ) : carregando ? (
        <div className="muted" style={{ padding: '32px 0', textAlign: 'center', fontSize: 13 }}>Carregando os títulos…</div>
      ) : base.length === 0 ? (
        <div className="muted" style={{ padding: '40px 0', textAlign: 'center', fontSize: 13 }}>
          {aba === 'caixa' ? 'Nenhum título no caixa sem conta — a linha “Sem conta” está zerada.' : 'Nenhum título fora do caixa sem conta.'}
        </div>
      ) : (
        <table className="tbl" style={{ fontSize: 12.5 }}>
          <thead><tr>
            <th style={{ width: 28 }}><input type="checkbox" checked={todosVisiveis} onChange={toggleVisiveis} title="Marcar os visíveis" /></th>
            {th('Data', 'data')}<th></th><th>Descrição</th><th>Contraparte</th><th>Forma</th>{th('Valor', 'valor')}
          </tr></thead>
          <tbody>
            {visiveis.map(it => (
              <tr key={it.key} className="clickable" onClick={() => toggle(it.key)}>
                <td onClick={e => e.stopPropagation()}><input type="checkbox" checked={sel.has(it.key)} onChange={() => toggle(it.key)} /></td>
                <td className="muted">{it.data ? fmtDate(it.data) : '—'}</td>
                <td><Icon name={it.sinal > 0 ? 'arrow-up-right' : 'arrow-down-left'} size={13}
                  style={{ color: it.sinal > 0 ? 'var(--tm-success)' : 'var(--tm-danger)' }} /></td>
                <td>{it.desc}{it.interna ? ' ⇄' : ''}<div className="muted" style={{ fontSize: 10.5 }}>{it.status}{it.semData ? ' · sem data de pagamento' : ''}</div></td>
                <td>{it.contraparte}</td>
                <td className="muted">{it.forma || '—'}</td>
                <td className="num right" style={{ color: it.sinal > 0 ? 'var(--tm-success)' : 'var(--tm-danger)' }}>{scMoeda(it.sinal * it.valor)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
      <div className="muted" style={{ fontSize: 11.5, marginTop: 10 }}>
        {aba === 'caixa' ? 'No caixa' : 'Fora do caixa'}: {carregando ? '…' : somaAba.n} · líquido {carregando ? '…' : scMoeda(somaAba.liquido)}
        {onNav && <> · <a onClick={() => onNav('financeiro/contas')} style={{ cursor: 'pointer' }}>ver em Contas bancárias</a></>}
      </div>
    </div>
  );
}

Object.assign(window, { FilaSemConta, scItens, scResumo, scOrdena, scEfeito, scLer });
