/* global React, Icon, Card, PageHead, Button, EmptyState, brl,
          finNormaliza, CATS_FORA_DA_OPERACAO */

// ============================================================
// Financeiro › Despesas fora da operação
// ============================================================
// R$ 21.000 pagos ao investidor e R$ 18.986,66 de retirada de sócio estão em "Outros"
// — e não por descuido: NÃO EXISTIA PARA ONDE MANDAR. As 19 categorias da tabela eram
// todas operacionais, e o seletor só oferece o que está nela. Esta tela desfaz isso
// UMA LINHA POR VEZ, com a Naii decidindo cada uma.
//
// 🔴 O GUARDA "RODE O SQL PRIMEIRO": a ação grava um `categoria`, e ele só serve se
// existir em `categorias_financeiras` (senão os dois DREs até funcionam, pelo nome, mas
// o seletor mostra "fora da lista" e ninguém sabe por quê). Enquanto as três categorias
// não existirem, os botões ficam desligados e a faixa diz quais faltam.
//
// 🔴 DOIS GRUPOS QUE NÃO SE MISTURAM, e por isso os destinos são diferentes:
//   · SAÍDA AO INVESTIDOR — quem os DADOS dizem ser investidor (aportes_investidor e os
//     títulos com natureza de aporte/empréstimo), não uma lista de palavras minha.
//     Destino: Devolução de principal (a dívida diminui) ou Juros de investidor.
//   · RETIRADA DE SÓCIO — pela assinatura própria ("Retirada de sócio / pessoais") no
//     texto do lançamento. Destino: Retirada de sócio — capital que sai de vez, sem
//     dívida. NUNCA Devolução de principal.
//     ⚠️ A observação é mostrada INTEIRA e rotulada "observação do lançamento" — o dado
//     não carrega autoria. Das 12 retiradas, 8 tinham texto do chat de alimentação
//     ("28 contrapartes do extrato… ATENÇÃO: pod…"), e a tela dizia "obs. da Naii".
//     Quem decide é ela, olhando o texto todo — não um rótulo que afirma o que não sabe.
//   · CRONOLOGIA — "cita um investidor" só vira saída ao investidor se, na data do
//     pagamento, aquela pessoa JÁ tinha capital na empresa. Guilherme R$ 1.000 em 31/07
//     com aporte de 09/09 não é devolução de nada: fica pendente, com o motivo e a data.
//
// 🔴 PENDENTES DA NAII ficam VISÍVEIS, sem ação, com o motivo: os R$ 15.049 ("passagem
// A CLASSIFICAR"), os R$ 4.000 da Paloma (citam o Janderson nas observações) e o
// reembolso de viagem (reembolso não é devolução de capital). Por TEXTO que ela mesma
// escreveu, nunca por valor: R$ 15.049 perto de R$ 15.000 não é um par.
//
// 🔴 A FRASE É DUPLA, e os dois efeitos são diferentes (decisão do PM, 18/09/2026):
//   DRE de caixa ......... R$ 0,00 — o dinheiro continua saindo, só muda onde aparece
//   DRE de Relatórios .... o resultado do mês do VENCIMENTO melhora no valor da linha
// Só é verdade porque `CATS_FORA_DA_OPERACAO` já está nos dois DREs (d7da70b). Antes
// disso, a segunda metade seria uma promessa que o código contradiz.

const RS_ASSINATURA_RETIRADA = /retirada de socio|retirada \/ pessoais|pro.?labore|distribuicao de lucro/;
const RS_PENDENTES = [
  { casa: /passagem a classificar/, motivo: 'aguardando a Naii dizer o que a empresa pagou com isto' },
  { casa: /paloma/, motivo: 'cita o Janderson nas observações — é dinheiro dele saindo, ou pagamento a ela? pergunta na Naii' },
  { casa: /reembolso/, motivo: 'reembolso de despesa, não devolução de capital — fica onde está' },
];
const RS_DESTINOS = {
  investidor: ['Devolução de principal', 'Juros de investidor'],
  retirada:   ['Retirada de sócio'],
};

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

function rsTextoDaLinha(p) {
  return [p.fornecedor || p.forn, p.referencia || p.ref, p.observacoes].filter(Boolean).join(' | ');
}

// Quem é investidor/sócio segundo os DADOS — e DESDE QUANDO. Primeiro nome com 4+
// letras, normalizado, e a lista de aportes (data, valor) de cada pessoa.
// 🔴 A DATA IMPORTA (furo de 18/09/2026): a fila oferecia "Devolução de principal" para
// R$ 1.000 pagos ao Guilherme em 31/07 — e o aporte dele é de 09/09. Não se devolve
// principal que ainda não entrou, nem se paga juros sobre ele. Casar o nome sem olhar a
// data é "entrar por quem recebeu, não por o que é": o erro que a regra do § 2 proíbe,
// cometido uma rodada depois de ela ser escrita.
function rsInvestidores(aportes, recRows) {
  const tok = (nome) => finNormaliza(nome).split(/\s+/).filter(t => t.length >= 4)[0] || null;
  const porToken = {};
  const poe = (nome, data, valor) => {
    const t = tok(nome); if (!t) return;
    (porToken[t] = porToken[t] || []).push({ data: String(data || '').slice(0, 10) || null, valor: Number(valor) || 0, nome });
  };
  (aportes || []).forEach(a => { if (a && a.investidor_nome) poe(a.investidor_nome, a.data_aporte, a.valor); });
  (recRows || []).forEach(r => {
    if (r && (r.natureza === 'aporte' || r.natureza === 'emprestimo') && r.cliente_nome)
      poe(r.cliente_nome, r.data_pagamento, Number(r.valor_recebido) > 0 ? r.valor_recebido : r.valor);
  });
  return { tokens: Object.keys(porToken), porToken };
}
// Quanto capital daquela pessoa estava na empresa NA DATA: soma dos aportes com data
// anterior ou igual. Zero = naquele dia ela ainda não era investidora.
function rsCapitalNaData(inv, token, data) {
  if (!inv || !data) return 0;
  return ((inv.porToken || {})[token] || []).filter(a => a.data && a.data <= data).reduce((s, a) => s + a.valor, 0);
}
function rsPrimeiroAporte(inv, token) {
  const ds = (((inv && inv.porToken) || {})[token] || []).map(a => a.data).filter(Boolean).sort();
  return ds[0] || null;
}
function rsDiaTxt(d) { return d ? d.slice(8, 10) + '/' + d.slice(5, 7) + '/' + d.slice(0, 4) : 'sem data'; }

function rsCandidatos(pagRows, inv) {
  const fora = window.CATS_FORA_DA_OPERACAO || {};
  const toks = (inv && inv.tokens) || [];
  const candidatos = [], pendentes = [];
  (pagRows || []).forEach(p => {
    if (!p) return;
    const cat = p.categoria || p.cat || '';
    if (fora[cat]) return;                                   // já está certa
    if (cat === 'Taxa de marketplace') return;               // a loja "NG Outlet" não é o sócio
    const texto = rsTextoDaLinha(p);
    const n = finNormaliza(texto);
    const base = {
      id: p.id, texto, linha: p,
      valor: Number(p.valor) || 0,
      vencimento: String(p.vencimento || '').slice(0, 10) || null,
      pagamento: String(p.data_pagamento || '').slice(0, 10) || null,
      status: p.status || 'Em aberto',
      categoria: cat || '(sem categoria)',
      observacoes: p.observacoes || '',
    };
    const pend = RS_PENDENTES.find(x => x.casa.test(n));
    const ehRetirada = RS_ASSINATURA_RETIRADA.test(n);
    const citados = toks.filter(tk => n.indexOf(tk) >= 0);
    // CRONOLOGIA: só é saída ao investidor se, na data do pagamento (ou do vencimento,
    // na falta dele), a pessoa citada já tinha capital na empresa.
    const dataRef = base.pagamento || base.vencimento || null;
    const comCapital = citados.filter(tk => rsCapitalNaData(inv, tk, dataRef) > 0);
    if (!ehRetirada && !citados.length) return;
    if (pend) { pendentes.push(Object.assign(base, { motivo: pend.motivo })); return; }
    // Retirada vence: uma linha que cita o sócio E diz "retirada" é retirada.
    if (ehRetirada) { candidatos.push(Object.assign(base, { grupo: 'retirada' })); return; }
    if (comCapital.length) { candidatos.push(Object.assign(base, { grupo: 'investidor' })); return; }
    // Cita um investidor, mas naquela data ele ainda não tinha capital aqui. Devolução e
    // juros são impossíveis; retirada ou reembolso, quem decide é o texto — e a Naii.
    const tk = citados[0];
    const quem = ((((inv && inv.porToken) || {})[tk] || [])[0] || {}).nome || tk;
    const primeiro = rsPrimeiroAporte(inv, tk);
    pendentes.push(Object.assign(base, { motivo: !dataRef
      ? 'cita ' + quem + ', mas a linha não tem data de pagamento nem vencimento — sem data não dá para saber se já havia capital'
      : 'pago em ' + rsDiaTxt(dataRef) + ', antes de qualquer aporte de ' + quem
        + (primeiro ? ' (o primeiro é de ' + rsDiaTxt(primeiro) + ')' : '')
        + ' — não é devolução nem juros; retirada ou reembolso, decida pelo texto' }));
  });
  const ordem = { investidor: 0, retirada: 1 };
  candidatos.sort((a, b) => (ordem[a.grupo] - ordem[b.grupo]) || (b.valor - a.valor));
  pendentes.sort((a, b) => b.valor - a.valor);
  return { candidatos, pendentes };
}

// O efeito nos DOIS DREs — a tela promete isto antes do clique.
// Caixa: sempre zero (mesma linha, mesmo valor, mesma data; só muda de onde aparece).
// Competência: sai das Despesas Operacionais do mês do VENCIMENTO — se não há
// vencimento, nunca entrou lá, então também não sai.
function rsImpacto(c) {
  if (!c) return { caixa: 0, competencia: 0, mes: null };
  return { caixa: 0, competencia: c.vencimento ? c.valor : 0, mes: c.vencimento ? c.vencimento.slice(0, 7) : null };
}
function rsMesTxt(mes) { return mes ? mes.slice(5, 7) + '/' + mes.slice(0, 4) : 'sem vencimento'; }
function rsPodeAgir(destino, categoriasAtivas) { return (categoriasAtivas || []).indexOf(destino) >= 0; }

// A frase dupla, no ESCOPO DO MÓDULO — componente declarado dentro de outro vira um
// tipo novo a cada render e remonta a subárvore (CLAUDE.md § 7, o caso do `Num`).
function RsFrase({ c }) {
  const imp = rsImpacto(c);
  return (
    <div className="muted" style={{ fontSize: 10.5, marginTop: 3, lineHeight: 1.5 }}>
      DRE de caixa: <b>R$ 0,00</b> · DRE de Relatórios: {imp.mes
        ? <>o resultado de <b>{rsMesTxt(imp.mes)}</b> melhora em <b>R$ {brl(imp.competencia)}</b></>
        : <>sem vencimento — nunca entrou no DRE de competência, então não sai</>}
    </div>
  );
}

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

function ReclassificarSaidas({ onNav }) {
  const [dados, setDados] = React.useState(null);        // { pag, cats, tokens }
  const [erro, setErro] = React.useState('');
  const [feito, setFeito] = React.useState([]);
  const [ocupado, setOcupado] = React.useState('');
  const [recarga, setRecarga] = React.useState(0);

  React.useEffect(() => {
    let vivo = true;
    if (!window.tmSupabase) { setDados({ pag: [], cats: [], tokens: [] }); return undefined; }
    setErro('');
    (async () => {
      const sb = window.tmSupabase;
      const [cp, cf, ap, cr] = await Promise.all([
        sb.from('contas_pagar').select('*'),
        sb.from('categorias_financeiras').select('nome, situacao').eq('situacao', 'Ativa'),
        sb.from('aportes_investidor').select('investidor_nome, valor, data_aporte'),
        sb.from('contas_receber').select('cliente_nome, natureza, valor, valor_recebido, data_pagamento').in('natureza', ['aporte', 'emprestimo']),
      ]);
      // 🔴 Erro de leitura NUNCA vira lista vazia: lista vazia aqui diz "não há nada a
      // consertar" — e a de categorias vazia ligaria o guarda por engano.
      const falhou = [cp, cf, ap, cr].map(r => r && r.error).find(Boolean);
      if (falhou) { if (vivo) { setErro(String(falhou.message || falhou)); setDados(null); } return; }
      if (!vivo) return;
      setDados({
        pag: cp.data || [],
        cats: (cf.data || []).map(c => c.nome),
        inv: rsInvestidores(ap.data || [], cr.data || []),
      });
    })().catch(e => { if (vivo) { setErro(String((e && e.message) || e)); setDados(null); } });
    return () => { vivo = false; };
  }, [recarga]);

  const m = React.useMemo(() => (dados ? rsCandidatos(dados.pag, dados.inv) : null), [dados]);
  const TRES = Object.keys(window.CATS_FORA_DA_OPERACAO || {});
  const faltam = dados ? TRES.filter(n => !rsPodeAgir(n, dados.cats)) : TRES;
  const sqlRodou = dados && faltam.length === 0;

  const aplicar = async (c, destino) => {
    if (!window.tmSupabase || !dados || !rsPodeAgir(destino, dados.cats)) return;
    setOcupado(c.id); setErro('');
    const { error } = await window.tmSupabase.from('contas_pagar').update({ categoria: destino }).eq('id', c.id);
    setOcupado('');
    if (error) { setErro('Não consegui reclassificar "' + c.texto.slice(0, 50) + '": ' + (error.message || error)); return; }
    const imp = rsImpacto(c);
    setFeito(f => f.concat([{ id: c.id, texto: c.texto, valor: c.valor, destino, competencia: imp.competencia, mes: imp.mes }]));
    setRecarga(k => k + 1);
  };

  return (
    <div className="canvas">
      <PageHead title="Despesas fora da operação"
        sub="saídas ao investidor e retiradas de sócio lançadas como gasto — uma linha por vez, você decide cada uma" />

      {dados && !sqlRodou ? (
        <div className="bal-aviso danger" style={{ marginBottom: 16 }}>
          <Icon name="alert-circle" size={15} />
          <div>
            <b>Rode <code>SUPABASE_categorias-fora-da-operacao.sql</code> primeiro.</b> Falta{faltam.length === 1 ? '' : 'm'} no
            cadastro: <b>{faltam.join(', ')}</b>. Sem a categoria na tabela, o seletor não a conhece e a linha
            reclassificada apareceria como “fora da lista”. Os botões ficam desligados até lá — a lista abaixo já
            mostra o que vai ser decidido.
          </div>
        </div>
      ) : null}

      <div className="bal-aviso warning" style={{ marginBottom: 16 }}>
        <Icon name="alert-triangle" size={15} />
        <div>
          <b>Dois grupos que não se misturam.</b>
          <div style={{ marginTop: 4 }}>
            · <b>Saída ao investidor</b> — quem os dados dizem ser investidor. Vira <b>Devolução de principal</b> (a dívida
            diminui) ou <b>Juros de investidor</b>. Aparece no bloco de financiamento do DRE de caixa.
          </div>
          <div style={{ marginTop: 4 }}>
            · <b>Retirada de sócio</b> — capital que sai de vez, sem dívida. Linha própria abaixo do resultado,
            <b> fora</b> do financiamento. Nunca vira Devolução de principal.
          </div>
          <div style={{ marginTop: 6, color: 'var(--tm-fg-3)' }}>
            Em todas: o DRE de caixa não muda (R$ 0,00 — o dinheiro continua saindo); o DRE de Relatórios melhora
            no valor da linha, <b>no mês do vencimento</b>. Não existe “aplicar em todas” de propósito.
          </div>
        </div>
      </div>

      {erro ? (
        <div className="bal-aviso danger" style={{ marginBottom: 14 }}>
          <Icon name="alert-circle" size={15} /><span>{erro}</span>
        </div>
      ) : null}

      {feito.length > 0 ? (
        <div className="bal-aviso" style={{ marginBottom: 14 }}>
          <Icon name="check-circle" size={15} style={{ color: 'var(--tm-success)' }} />
          <div>
            <b>{feito.length} linha(s) reclassificada(s) nesta sessão</b> — R$ {brl(feito.reduce((a, f) => a + f.valor, 0))}.
            DRE de caixa: R$ 0,00. DRE de Relatórios: {feito.filter(f => f.mes).map(f => rsMesTxt(f.mes) + ' +R$ ' + brl(f.competencia)).join(' · ') || 'nenhuma com vencimento'}.
          </div>
        </div>
      ) : null}

      {dados === null && !erro ? (
        <div style={{ padding: '48px 0', textAlign: 'center', color: 'var(--tm-fg-3)' }}>
          <Icon name="loader" size={20} style={{ opacity: .4 }} />
        </div>
      ) : !m || (m.candidatos.length === 0 && m.pendentes.length === 0) ? (
        <EmptyState icon="check-circle" title="Nenhuma despesa fora da operação em Outros"
          msg="Nenhum lançamento de Contas a Pagar cita um investidor conhecido nem tem assinatura de retirada de sócio." />
      ) : (<>
        <Card title={m.candidatos.length + ' despesa(s) a decidir'}
          action={<span className="muted" style={{ fontSize: 11 }}>
            {m.candidatos.filter(c => c.grupo === 'investidor').length} ao investidor · {m.candidatos.filter(c => c.grupo === 'retirada').length} retirada de sócio
          </span>}>
          <table className="tbl" style={{ fontSize: 12.5 }}>
            <thead><tr>
              <th scope="col">Despesa</th>
              <th scope="col" style={{ width: 120 }}>Grupo</th>
              <th scope="col" className="num right" style={{ width: 120 }}>Valor</th>
              <th scope="col" style={{ width: 100 }}>Vencimento</th>
              <th scope="col" style={{ width: 330 }}>Vira</th>
            </tr></thead>
            <tbody>
              {m.candidatos.map(c => (
                <tr key={c.id}>
                  <th scope="row" style={{ fontWeight: 400 }}>
                    {c.texto.slice(0, 80)}
                    <div className="muted" style={{ fontSize: 10.5 }}>{c.categoria} · {c.status}</div>
                    {/* 🔴 A tela NÃO sabe quem escreveu a observação — o dado não carrega
                        autoria. "obs. da Naii" convidava a clicar num rótulo que não era dela:
                        as 8 retiradas que sobraram vieram do chat de alimentação. E INTEIRA:
                        o corte em 110 caracteres escondia o "ATENÇÃO" de quem agrupou — o mesmo
                        corte de 72 que escondeu o Janderson na linha da Paloma. */}
                    {c.observacoes ? (
                      <div className="muted" style={{ fontSize: 10.5, fontStyle: 'italic', whiteSpace: 'pre-wrap' }}>observação do lançamento: “{c.observacoes}”</div>
                    ) : null}
                  </th>
                  <td>{c.grupo === 'investidor' ? 'saída ao investidor' : 'retirada de sócio'}</td>
                  <td className="num right">R$ {brl(c.valor)}</td>
                  <td className="muted">{c.vencimento ? c.vencimento.slice(8, 10) + '/' + c.vencimento.slice(5, 7) + '/' + c.vencimento.slice(0, 4) : 'sem vencimento'}</td>
                  <td>
                    <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                      {RS_DESTINOS[c.grupo].map(destino => (
                        <Button key={destino} size="sm" variant="secondary"
                          disabled={!sqlRodou || !rsPodeAgir(destino, dados.cats) || ocupado === c.id}
                          title={rsPodeAgir(destino, dados.cats) ? ('Reclassificar para ' + destino) : ('Falta a categoria "' + destino + '" no cadastro — rode o SQL')}
                          onClick={() => aplicar(c, destino)}>
                          {ocupado === c.id ? '…' : destino}
                        </Button>
                      ))}
                    </div>
                    <RsFrase c={c} />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </Card>

        {m.pendentes.length > 0 ? (
          <Card title={m.pendentes.length + ' linha(s) esperando a Naii'}
            action={<span className="muted" style={{ fontSize: 11 }}>mostradas de propósito — ausência silenciosa é o que esta tela existe para não ter</span>}>
            <table className="tbl" style={{ fontSize: 12.5 }}>
              <thead><tr>
                <th scope="col">Despesa</th>
                <th scope="col" className="num right" style={{ width: 120 }}>Valor</th>
                <th scope="col" style={{ width: 100 }}>Pagamento</th>
                <th scope="col" style={{ width: 420 }}>Por que não tem botão</th>
              </tr></thead>
              <tbody>
                {m.pendentes.map(c => (
                  <tr key={c.id}>
                    <th scope="row" style={{ fontWeight: 400 }}>
                      {c.texto.slice(0, 80)}<div className="muted" style={{ fontSize: 10.5 }}>{c.categoria} · {c.status}</div>
                      {c.observacoes ? (
                        <div className="muted" style={{ fontSize: 10.5, fontStyle: 'italic', whiteSpace: 'pre-wrap' }}>observação do lançamento: “{c.observacoes}”</div>
                      ) : null}
                    </th>
                    <td className="num right">R$ {brl(c.valor)}</td>
                    <td className="muted">{c.pagamento ? c.pagamento.slice(8, 10) + '/' + c.pagamento.slice(5, 7) + '/' + c.pagamento.slice(0, 4) : 'sem data'}</td>
                    <td className="muted">{c.motivo}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </Card>
        ) : null}

        <p className="fc-nota">
          Reclassificar troca só a coluna <b>categoria</b> — mesma linha, mesmo valor, mesma data, mesma conta. O saldo
          das contas não muda. Os dois DREs já sabem tratar as três categorias pelo nome; o cadastro delas na tabela é o
          que faz o seletor conhecê-las também.
        </p>
      </>)}
    </div>
  );
}

Object.assign(window, {
  ReclassificarSaidas,
  rsInvestidores, rsCapitalNaData, rsPrimeiroAporte, rsDiaTxt,
  rsCandidatos, rsImpacto, rsMesTxt, rsPodeAgir, rsTextoDaLinha,
});
