// js/components/widgets/Gastos.jsx
// [v216 20260512] Gastos ZAYNEX-level — 3 abas + Folha de Pagamento + Dashboard + Recibo PDF
// Refac total. Anterior: Fase 5 (2026-04-29) — CRUD simples + filtros.
// Aprovado Jamal 2026-05-12: 1=B (3 abas) · 2=A (auto-folha+manual) · 3=A (monthly_salary em app_users) · 4=A (recibo minimalista) · 5=A (12 categorias)
// Deps runtime: Modal, Icon, showConfirm, fmt, fmtDate, dualWrite, genIdUUID, today, SmartSelect, znxGuard, sb, toast, Sentry, pdfShared
(function() {
  'use strict';
  const {useState, useEffect, useRef, useCallback, useMemo} = React;

  // [v216] 12 categorias padrão ZAYNEX (substitui hardcoded 'Aluguel')
  const CATEGORIAS_V216 = [
    'Folha de Pagamento','Aluguel','Energia','Água','Internet/Telefone',
    'Fretes','Marketing','Sistema/Software','Material Escritório',
    'Manutenção/Limpeza','Impostos','Outros'
  ];
  const CAT_COLORS = {
    'Folha de Pagamento':'#7C3AED','Aluguel':'#EA580C','Energia':'#F59E0B',
    'Água':'#06B6D4','Internet/Telefone':'#2563EB','Fretes':'#16A34A',
    'Marketing':'#EC4899','Sistema/Software':'#8B5CF6','Material Escritório':'#84CC16',
    'Manutenção/Limpeza':'#0891B2','Impostos':'#DC2626','Outros':'#6B7280'
  };
  const METODOS = ['Pix','Dinheiro','Transferência','Cartão','Boleto'];
  const RECORRENCIAS = ['Único','Mensal','Quinzenal','Anual','Fixo','Variável']; // mantém Fixo/Variável legacy

  // [F5-03 v223.28] deleteGastoRef.current + payrollRef.current movidos pra useRef DENTRO do componente
  // (padrão BUG-R001 fixado em Vendas/Orcamentos v218.13 — let module-level compartilha state entre instances/tabs)

function Gastos({gastos, setGastos, user, allUsers, setAllUsers}){
  const isAdmin = user?.role === 'admin' || user?.role === 'financeiro';
  const[tab, setTab] = useState(()=>{ try{ return localStorage.getItem('znx_gastos_tab')||'lancamentos'; }catch(_){return'lancamentos';} });
  useEffect(()=>{ try{ localStorage.setItem('znx_gastos_tab', tab); }catch(_){} }, [tab]);

  // ─── STATE: Lançamentos ────────────────────────────────────────────
  const[modal, setModal] = useState(null);
  const[form, setForm] = useState({
    description:'', category:'Folha de Pagamento', value:'', date:today(),
    recurrence:'Mensal', status:'Pendente', notes:'',
    beneficiary_name:'', beneficiary_user_id:'', payment_method:'Pix'
  });
  const[filter, setFilter] = useState({month:today().slice(0,7), category:'', status:'', method:''});
  const[editId, setEditId] = useState(null);

  // ─── STATE: Folha ──────────────────────────────────────────────────
  const[salaryEditUser, setSalaryEditUser] = useState(null); // user obj sendo editado
  const[salaryEditValue, setSalaryEditValue] = useState('');
  const[payrollMonth, setPayrollMonth] = useState(today().slice(0,7));
  const[payrollInflight, setPayrollInflight] = useState(false);
  const[bulkPayInflight, setBulkPayInflight] = useState(false);

  // ─── STATE: v217 Pagamento parcial + recibos ──────────────────────
  const[payModal, setPayModal] = useState(null); // {gasto, remaining} | null
  const[payForm, setPayForm] = useState({value:'', method:'Pix', notes:''});
  const[payInflight, setPayInflight] = useState(false);
  const[gastoPayments, setGastoPayments] = useState([]); // todos payments (1 fetch)
  const[loadingPayments, setLoadingPayments] = useState(false);
  const[receiptListModal, setReceiptListModal] = useState(null); // {gasto, payments[]} | null
  const[searchTerm, setSearchTerm] = useState('');
  const[filter7d, setFilter7d] = useState(false);

  // Fetch payments do mês ao trocar filter.month
  useEffect(()=>{
    if(typeof sb==='undefined'||typeof sb.from!=='function')return;
    if(tab!=='lancamentos' && tab!=='folha') return;
    setLoadingPayments(true);
    // Pega payments dos últimos 90 dias (cobre filtros típicos)
    const since = new Date(Date.now()-90*86400000).toISOString();
    sb.from('gasto_payments').select('id,gasto_id,value,paid_at,payment_method,receipt_number,notes,deleted_at').gte('paid_at',since).is('deleted_at',null).order('paid_at',{ascending:false}).then(({data,error})=>{
      if(error){
        console.error('[ZNX v217] fetch gasto_payments',error);
        if(typeof Sentry!=='undefined')try{Sentry.captureException(error,{tags:{feature:'gastos_payments_fetch'}});}catch(_){}
      }else{
        setGastoPayments(data||[]);
      }
      setLoadingPayments(false);
    });
  },[tab,filter.month]);

  const paymentsByGasto = useMemo(()=>{
    const map = {};
    (gastoPayments||[]).forEach(p=>{
      if(!map[p.gasto_id]) map[p.gasto_id] = [];
      map[p.gasto_id].push(p);
    });
    return map;
  },[gastoPayments]);

  // ─── REFS pra inflight (regra_loading_state_obrigatorio) ───────────
  const savingRef = useRef(false);
  const deleteGastoRef = useRef(false);    // [F5-03 v223.28] era let module-level
  const payrollRef = useRef(false);        // [F5-03 v223.28] era let module-level
  useEffect(()=>{ savingRef.current = false; }, [modal]);
  const togglePagoInflightRef = useRef(new Set());
  const [togglePagoVer, setTogglePagoVer] = useState(0);
  const reciboInflightRef = useRef(new Set());
  const [reciboVer, setReciboVer] = useState(0);

  // ─── DERIVED Lançamentos ───────────────────────────────────────────
  const filtered = useMemo(()=>{
    const today_d = new Date().toISOString().slice(0,10);
    const cutoff7d = new Date(Date.now()+7*86400000).toISOString().slice(0,10);
    const term = String(searchTerm||'').trim().toLowerCase();
    return gastos.filter(g=>{
      if(g.deleted_at) return false;
      if(filter.month && !String(g.date||'').startsWith(filter.month)) return false;
      if(filter.category && g.category !== filter.category) return false;
      if(filter.status && g.status !== filter.status) return false;
      if(filter.method && (g.payment_method||g.paymentMethod||'') !== filter.method) return false;
      if(filter7d){
        // só Pendentes/Parciais vencendo até 7 dias
        if(g.status !== 'Pendente' && g.status !== 'Parcial') return false;
        if(!g.date || g.date > cutoff7d) return false;
      }
      if(term){
        const hay = (g.description+' '+(g.beneficiary_name||'')+' '+(g.notes||'')+' '+(g.category||'')).toLowerCase();
        if(!hay.includes(term)) return false;
      }
      return true;
    });
  }, [gastos, filter, searchTerm, filter7d]);

  const totalMes = filtered.reduce((s,g)=>s+(Number(g.value)||0), 0);
  const totalPago = filtered.filter(g=>g.status==='Pago').reduce((s,g)=>s+(Number(g.value)||0), 0);
  const totalPendente = filtered.filter(g=>g.status==='Pendente').reduce((s,g)=>s+(Number(g.value)||0), 0);
  const totalFolha = filtered.filter(g=>g.category==='Folha de Pagamento').reduce((s,g)=>s+(Number(g.value)||0), 0);

  const porCategoria = useMemo(()=>{
    const map = {};
    filtered.forEach(g=>{ if(g.status!=='Cancelado') map[g.category] = (map[g.category]||0) + Number(g.value||0); });
    return Object.entries(map).sort((a,b)=>b[1]-a[1]);
  }, [filtered]);

  // ─── DERIVED Folha de Pagamento ────────────────────────────────────
  const employeesWithSalary = useMemo(()=>{
    return (allUsers||[]).filter(u=>u.active && Number(u.monthly_salary||0) > 0)
      .sort((a,b)=>(b.monthly_salary||0)-(a.monthly_salary||0));
  }, [allUsers]);

  const gastosFolhaDoMes = useMemo(()=>{
    const m = payrollMonth;
    return (gastos||[]).filter(g=>!g.deleted_at && g.category==='Folha de Pagamento' && String(g.date||'').startsWith(m));
  }, [gastos, payrollMonth]);

  const folhaStats = useMemo(()=>{
    const map = {};
    gastosFolhaDoMes.forEach(g=>{
      if(g.beneficiary_user_id){ map[g.beneficiary_user_id] = g; }
    });
    return map; // {user_id: gasto}
  }, [gastosFolhaDoMes]);

  const folhaTotal = useMemo(()=>{
    return employeesWithSalary.reduce((s,u)=>s + Number(u.monthly_salary||0), 0);
  }, [employeesWithSalary]);

  const folhaPagoMes = useMemo(()=>{
    return gastosFolhaDoMes.filter(g=>g.status==='Pago').reduce((s,g)=>s+Number(g.value||0),0);
  }, [gastosFolhaDoMes]);

  // ─── DERIVED Dashboard ─────────────────────────────────────────────
  const ultimos6Meses = useMemo(()=>{
    const map = {};
    const now = new Date();
    for(let i=5;i>=0;i--){
      const d = new Date(now.getFullYear(), now.getMonth()-i, 1);
      const k = d.toISOString().slice(0,7);
      map[k] = 0;
    }
    (gastos||[]).forEach(g=>{
      if(g.deleted_at || g.status==='Cancelado') return;
      const k = String(g.date||'').slice(0,7);
      if(k in map) map[k] += Number(g.value||0);
    });
    return Object.entries(map).map(([mes,val])=>({mes,val}));
  }, [gastos]);

  const mesAnteriorVal = ultimos6Meses.length>=2 ? ultimos6Meses[ultimos6Meses.length-2].val : 0;
  const variacaoVsMesAnt = mesAnteriorVal > 0 ? ((totalMes-mesAnteriorVal)/mesAnteriorVal*100) : 0;

  const top5Beneficiarios = useMemo(()=>{
    const map = {};
    (gastos||[]).forEach(g=>{
      if(g.deleted_at || g.status==='Cancelado') return;
      const k = String(g.date||'').slice(0,7);
      if(k !== filter.month) return;
      const ben = g.beneficiary_name || g.description?.substring(0,30) || '—';
      map[ben] = (map[ben]||0) + Number(g.value||0);
    });
    return Object.entries(map).sort((a,b)=>b[1]-a[1]).slice(0,5);
  }, [gastos, filter.month]);

  // ─── HANDLERS LANÇAMENTOS ──────────────────────────────────────────
  function openNew(){
    setForm({
      description:'', category:'Folha de Pagamento', value:'', date:today(),
      recurrence:'Mensal', status:'Pendente', notes:'',
      beneficiary_name:'', beneficiary_user_id:'', payment_method:'Pix'
    });
    setEditId(null);
    setModal('form');
  }
  function openEdit(g){
    setForm({
      ...g,
      value: String(g.value||''),
      beneficiary_name: g.beneficiary_name || '',
      beneficiary_user_id: g.beneficiary_user_id || '',
      payment_method: g.payment_method || g.paymentMethod || 'Pix'
    });
    setEditId(g.id);
    setModal('form');
  }

  // Auto-fill beneficiário quando escolhe user da Folha
  function onBeneficiaryUserChange(uid){
    const u = (allUsers||[]).find(x=>x.id===uid);
    setForm(f=>({
      ...f,
      beneficiary_user_id: uid,
      beneficiary_name: u?.name || f.beneficiary_name,
      value: (!f.value && u?.monthly_salary) ? String(u.monthly_salary) : f.value,
      description: (!f.description && u?.name) ? `Salário ${u.name} · ${(form.date||today()).slice(0,7)}` : f.description
    }));
  }

  async function save(){
    if(savingRef.current) return;
    if(!form.description || !form.value){ toast('Preencha descrição e valor','warning'); return; }
    if(form.category==='Folha de Pagamento' && !form.beneficiary_name){
      toast('Folha de Pagamento exige nome do beneficiário','warning'); return;
    }
    savingRef.current = true;
    try{
      const isNew = !editId;
      const id = isNew ? genIdUUID() : editId;
      const entry = {
        description: form.description,
        category: form.category,
        value: Number(form.value),
        date: form.date,
        recurrence: form.recurrence,
        status: form.status,
        notes: form.notes || null,
        beneficiary_name: form.beneficiary_name || null,
        beneficiary_user_id: form.beneficiary_user_id || null,
        payment_method: form.payment_method || null
      };
      const ok = await dualWrite('gastos', id, entry, isNew, ()=>{
        if(!isNew){ setGastos(prev=>prev.map(g=>g.id===id?{...g,...entry,id}:g)); }
        else{ setGastos(prev=>[...prev, {...entry, id, deleted_at:null}]); }
      });
      if(!ok){ savingRef.current = false; return; }
      setModal(null);
      toast(isNew?'✅ Gasto criado':'✅ Gasto atualizado','success');
    }catch(e){
      savingRef.current = false;
      toast('❌ '+(e.message||'Erro inesperado'),'error');
      if(typeof Sentry!=='undefined') Sentry.captureException(e,{extra:{context:'save_gasto_v216'}});
    }
  }

  async function remove(id){
    if(!await znxGuard(['admin','financeiro'])) return;
    if(deleteGastoRef.current){ toast('⏳ Processando...','info'); return; }
    if(!await showConfirm({
      title:'Cancelar Gasto',
      message:'Cancelar este gasto?\n(Mantém no histórico marcado Cancelado — compliance fiscal.)',
      confirmText:'Cancelar',confirmColor:'#DC2626'
    })) return;
    deleteGastoRef.current = true;
    try{
      const beforeRow = gastos.find(g=>g.id===id)||null;
      const {error} = await sb.from('gastos').update({status:'Cancelado',updated_at:new Date().toISOString()}).eq('id',id);
      if(error){
        toast('❌ Erro ao cancelar: '+error.message,'error');
        if(typeof Sentry!=='undefined') Sentry.captureException(new Error(error.message),{extra:{context:'cancelGasto_v216',id}});
        return;
      }
      setGastos(prev=>prev.map(g=>g.id===id?{...g,status:'Cancelado'}:g));
      try{
        await sb.rpc('log_admin_action',{
          p_action:'cancel',p_target_type:'gasto',p_target_id:String(id),
          p_before: beforeRow?{status:beforeRow.status,value:beforeRow.value}:null,
          p_after:{status:'Cancelado'},
          p_metadata:{description:beforeRow?.description,category:beforeRow?.category,context:'soft_delete_gasto_v216'}
        });
      }catch(_){/* audit best-effort */}
      toast('✅ Gasto cancelado','success');
    }finally{ deleteGastoRef.current = false; }
  }

  // [v217] Abre modal "Pagar quanto?" — substitui toggle binário antigo
  function openPayModal(g){
    if(g.status==='Cancelado') return;
    const paid = Number(g.paid_value||0);
    const total = Number(g.value||0);
    const remaining = Math.max(0, total - paid);
    if(remaining <= 0.01){
      toast('✓ Este gasto já está totalmente pago','info');
      return;
    }
    setPayModal({gasto:g, remaining});
    setPayForm({value: String(remaining.toFixed(2)), method: g.payment_method || 'Pix', notes:''});
  }

  // [v217] Confirma pagamento via RPC register_gasto_payment_v1
  async function confirmPayment(){
    if(!payModal) return;
    if(payInflight) return;
    if(!await znxGuard(['admin','financeiro'])) return;
    const v = Number(payForm.value||0);
    if(v <= 0){ toast('Valor deve ser maior que zero','warning'); return; }
    if(v > payModal.remaining + 0.01){
      toast(`Valor maior que saldo restante (${fmt(payModal.remaining)})`,'warning');
      return;
    }
    setPayInflight(true);
    // [RG7-V3 ONDA-N3 v218.20 — 2026-05-13] idempotency_key (regra_idempotency_padrao_unico)
    // gasto_payments.idempotency_key + UNIQUE PARTIAL INDEX criados em v218.15 ONDA-N1.
    // Drainer reenvia mesmo key → server retorna idempotent_replay=true em retry.
    const idemKeyGP = (typeof window.genIdempotencyKey==='function'?window.genIdempotencyKey():(typeof crypto?.randomUUID==='function'?crypto.randomUUID():null));
    const rpcArgs = {
      p_gasto_id: payModal.gasto.id,
      p_value: v,
      p_method: payForm.method,
      p_notes: payForm.notes || null,
      p_idempotency_key: idemKeyGP
    };
    try{
      // [RG7-V3] Early enqueue se offline (regra_pwa_offline_queue + regra_falha_silenciosa_proibida)
      if(typeof navigator!=='undefined' && navigator.onLine===false && idemKeyGP && window.znxOfflineQueue && window.znxOfflineQueue.isAvailable()){
        const enq = await window.znxOfflineQueue.enqueue('register_gasto_payment_v1', rpcArgs);
        if(enq){
          toast('💾 Pagamento salvo offline — vai sincronizar quando voltar internet','warning');
          if(typeof Sentry!=='undefined')Sentry.addBreadcrumb({category:'offline-queue',message:'gasto-payment queued offline',level:'info',data:{queueId:enq.id,idemKey:idemKeyGP,gastoId:payModal.gasto.id,value:v}});
          setPayModal(null);
          return;
        }
      }
      const {data, error} = await sb.rpc('register_gasto_payment_v1', rpcArgs);
      if(error){
        // [RG7-V3] Erro de rede pós-call → enfileira
        const isNet = /Failed to fetch|NetworkError|net::ERR/i.test(error.message||'');
        if(isNet && idemKeyGP && window.znxOfflineQueue && window.znxOfflineQueue.isAvailable()){
          const enq = await window.znxOfflineQueue.enqueue('register_gasto_payment_v1', rpcArgs);
          if(enq){
            toast('💾 Pagamento salvo offline — vai sincronizar quando voltar internet','warning');
            if(typeof Sentry!=='undefined')Sentry.addBreadcrumb({category:'offline-queue',message:'gasto-payment queued network_error',level:'info',data:{queueId:enq.id,idemKey:idemKeyGP}});
            setPayModal(null);
            return;
          }
        }
        toast('❌ '+error.message,'error');
        if(typeof Sentry!=='undefined')try{Sentry.captureException(error,{tags:{feature:'gasto_payment_v217'}});}catch(_){}
        return;
      }
      const r = data || {};
      if(r.success === false){
        toast('❌ '+(r.errorMessage||r.errorCode),'error'); return;
      }
      // [RG7-V3 v218.20] Idempotent replay — pagamento já existia, mas é success
      if(r.idempotent_replay){
        toast('✅ Pagamento já registrado (replay)','info');
        setPayModal(null);
        return;
      }
      // Update local
      setGastos(prev=>prev.map(x=>x.id===payModal.gasto.id?{...x,status:r.new_status,paid_value:r.paid_value_total}:x));
      // Refetch payments
      const since = new Date(Date.now()-90*86400000).toISOString();
      const {data:freshPay} = await sb.from('gasto_payments').select('id,gasto_id,value,paid_at,payment_method,receipt_number,notes,deleted_at').gte('paid_at',since).is('deleted_at',null).order('paid_at',{ascending:false});
      if(freshPay) setGastoPayments(freshPay);
      const msg = r.new_status==='Pago' ? `✅ Pago totalmente · Recibo ${r.receipt_number} gerado` : `✅ ${fmt(v)} pago · Recibo ${r.receipt_number} · Saldo ${fmt(r.remaining)}`;
      toast(msg,'success');
      setPayModal(null);
    }catch(e){
      // [RG7-V3 v218.20] Exception de rede → enfileira
      const isNet = /Failed to fetch|NetworkError|net::ERR/i.test(e?.message||'');
      if(isNet && idemKeyGP && window.znxOfflineQueue && window.znxOfflineQueue.isAvailable()){
        try{
          const enq = await window.znxOfflineQueue.enqueue('register_gasto_payment_v1', rpcArgs);
          if(enq){
            toast('💾 Pagamento salvo offline — vai sincronizar quando voltar internet','warning');
            if(typeof Sentry!=='undefined')Sentry.addBreadcrumb({category:'offline-queue',message:'gasto-payment queued exception',level:'info',data:{queueId:enq.id,idemKey:idemKeyGP}});
            setPayModal(null);
            return;
          }
        }catch(eEnq){znxLogWarn('[Gastos] enqueue fallback falhou', eEnq);}
      }
      toast('❌ Erro inesperado','error');
      if(typeof Sentry!=='undefined')try{Sentry.captureException(e,{tags:{feature:'gasto_payment_v217'}});}catch(_){}
    }finally{
      setPayInflight(false);
    }
  }

  // [v217] Pagar Folha toda do mês em 1 click
  async function bulkPayFolhaMes(){
    if(bulkPayInflight) return;
    if(!await znxGuard(['admin','financeiro'])) return;
    const pendentes = gastosFolhaDoMes.filter(g=>g.status==='Pendente'||g.status==='Parcial');
    if(pendentes.length===0){ toast('✓ Toda folha desse mês já está paga','info'); return; }
    const saldoTotal = pendentes.reduce((s,g)=>s + (Number(g.value||0) - Number(g.paid_value||0)), 0);
    if(!await showConfirm({
      title:`Pagar Folha de ${payrollMonth}`,
      message:`Vai registrar ${pendentes.length} pagamento(s) cobrindo saldo restante (${fmt(saldoTotal)} total) via Pix.\n\nCada funcionário recebe 1 recibo próprio. Continuar?`,
      confirmText:'Pagar Tudo',confirmColor:'#16A34A'
    })) return;
    setBulkPayInflight(true);
    try{
      const {data, error} = await sb.rpc('pay_full_payroll_month_v1',{p_month: payrollMonth, p_method:'Pix'});
      if(error){
        toast('❌ '+error.message,'error');
        if(typeof Sentry!=='undefined')try{Sentry.captureException(error,{tags:{feature:'bulk_pay_folha_v217'}});}catch(_){}
        return;
      }
      const r = data || {};
      if(r.success === false){ toast('❌ '+(r.errorMessage||r.errorCode),'error'); return; }
      toast(`✅ ${r.paid_count} pagamento(s) registrados · ${fmt(r.total_value)} total`,'success');
      // Refetch tudo
      const since = new Date(Date.now()-90*86400000).toISOString();
      const [pays, gs] = await Promise.all([
        sb.from('gasto_payments').select('id,gasto_id,value,paid_at,payment_method,receipt_number,notes,deleted_at').gte('paid_at',since).is('deleted_at',null).order('paid_at',{ascending:false}),
        sb.from('gastos').select('*').gte('date',payrollMonth+'-01').is('deleted_at',null)
      ]);
      if(pays.data) setGastoPayments(pays.data);
      if(gs.data){
        setGastos(prev=>{
          const map = new Map(prev.map(x=>[x.id,x]));
          gs.data.forEach(g=>map.set(g.id, {...map.get(g.id), ...g}));
          return [...map.values()];
        });
      }
    }catch(e){
      toast('❌ Erro inesperado','error');
      if(typeof Sentry!=='undefined')try{Sentry.captureException(e,{tags:{feature:'bulk_pay_folha_v217'}});}catch(_){}
    }finally{
      setBulkPayInflight(false);
    }
  }

  // [v217] Reverter Pago → Pendente: soft-delete últimas parcelas
  async function reverterPagamento(g){
    if(!await znxGuard(['admin','financeiro'])) return;
    const pays = (paymentsByGasto[g.id]||[]).filter(p=>!p.deleted_at);
    if(pays.length===0){ toast('Sem pagamentos pra reverter','warning'); return; }
    if(!await showConfirm({
      title:'Reverter Pagamento',
      message:`Cancelar TODOS os ${pays.length} pagamento(s) deste gasto? Os recibos ficam no histórico mas o gasto volta pra Pendente.`,
      confirmText:'Reverter',confirmColor:'#EA580C'
    })) return;
    try{
      // Soft-delete todos os payments deste gasto
      const ids = pays.map(p=>p.id);
      const {error} = await sb.from('gasto_payments').update({deleted_at:new Date().toISOString()}).in('id',ids);
      if(error){ toast('❌ '+error.message,'error'); return; }
      // Refetch payments
      const since = new Date(Date.now()-90*86400000).toISOString();
      const {data:freshPay} = await sb.from('gasto_payments').select('id,gasto_id,value,paid_at,payment_method,receipt_number,notes,deleted_at').gte('paid_at',since).is('deleted_at',null).order('paid_at',{ascending:false});
      if(freshPay) setGastoPayments(freshPay);
      setGastos(prev=>prev.map(x=>x.id===g.id?{...x,status:'Pendente',paid_value:0}:x));
      toast('✅ Pagamento revertido','success');
    }catch(e){
      toast('❌ Erro','error');
      if(typeof Sentry!=='undefined')try{Sentry.captureException(e,{tags:{feature:'reverter_pag_v217'}});}catch(_){}
    }
  }

  // ─── HANDLER: Gerar Recibo PDF (v217 — aceita payment + mode) ─────
  async function gerarRecibo(g, payment, mode){
    const flightKey = (payment?.id || g.id) + ':' + (mode||'download');
    if(reciboInflightRef.current.has(flightKey)) return;
    if(typeof window.pdfShared?.gerarReciboPagamento !== 'function'){
      toast('❌ pdfShared não carregado. Recarregue (Ctrl+Shift+R)','error'); return;
    }
    reciboInflightRef.current.add(flightKey);
    setReciboVer(v=>v+1);
    try{
      // Refetch fresh (regra_fresh_select_antes_update)
      const {data: fresh, error} = await sb.from('gastos').select('*').eq('id',g.id).maybeSingle();
      const gastoFinal = (!error && fresh) ? {...g, ...fresh} : g;
      // Calcula remaining se payment fornecido
      let remaining = null;
      if(payment){
        // Soma de TODOS payments do gasto não-deletados ATÉ esta parcela (inclusive)
        const allPays = (paymentsByGasto[g.id]||[]).filter(p=>!p.deleted_at);
        const paidIncluding = allPays
          .filter(p=>new Date(p.paid_at).getTime() <= new Date(payment.paid_at).getTime())
          .reduce((s,p)=>s+Number(p.value||0),0);
        remaining = Math.max(0, Number(gastoFinal.value||0) - paidIncluding);
      }
      await window.pdfShared.gerarReciboPagamento({
        gasto: gastoFinal,
        payment: payment || null,
        remaining,
        mode: mode || 'download'
      });
      const recNum = payment?.receipt_number || gastoFinal.receipt_number || '(sem nº)';
      toast(`📄 Recibo ${recNum} ${mode==='print'?'enviado pra impressão':'baixado'}`,'success');
    }catch(e){
      toast('❌ Erro ao gerar recibo','error');
      if(typeof Sentry!=='undefined') Sentry.captureException(e,{extra:{context:'gerarRecibo_v217',id:g.id,mode}});
    }finally{
      reciboInflightRef.current.delete(flightKey);
      setReciboVer(v=>v+1);
    }
  }

  // [v217] Click no botão recibo abre modal sempre (mesmo com 1 pagamento) — dá escolha Imprimir vs Baixar
  function openReceiptOrList(g){
    const pays = (paymentsByGasto[g.id]||[]).filter(p=>!p.deleted_at);
    if(pays.length === 0){
      toast('⚠️ Nenhum pagamento registrado ainda','warning'); return;
    }
    setReceiptListModal({gasto:g, payments:pays});
  }

  // ─── HANDLER: Folha — salvar salário ───────────────────────────────
  async function saveSalary(){
    if(!salaryEditUser) return;
    if(!await znxGuard(['admin','financeiro'])) return;
    const val = Number(salaryEditValue||0);
    if(val < 0){ toast('Salário não pode ser negativo','warning'); return; }
    try{
      const {error} = await sb.from('app_users').update({monthly_salary: val, updated_at: new Date().toISOString()}).eq('id', salaryEditUser.id);
      if(error){ toast('❌ '+error.message,'error'); return; }
      if(typeof setAllUsers === 'function'){
        setAllUsers(prev=>prev.map(u=>u.id===salaryEditUser.id?{...u, monthly_salary: val}:u));
        toast(`✅ Salário de ${salaryEditUser.name} atualizado pra ${fmt(val)}`,'success');
      }else{
        // [v216.1 hotfix] allUsers é useMemo derivado no App.jsx — sem setter direto.
        // Banco salvou ✅, UI atualiza no próximo refetch global (refresh ou cutover sync).
        toast(`✅ Salário salvo no banco · recarregue (F5) pra ver atualizado na lista`,'success');
      }
      setSalaryEditUser(null);
      setSalaryEditValue('');
    }catch(e){
      toast('❌ Erro','error');
      if(typeof Sentry!=='undefined') Sentry.captureException(e,{extra:{context:'saveSalary_v216'}});
    }
  }

  // ─── HANDLER: Gerar Folha do Mês ──────────────────────────────────
  async function gerarFolhaMes(){
    if(payrollRef.current){ toast('⏳ Já gerando...','info'); return; }
    if(!await znxGuard(['admin','financeiro'])) return;
    const employees = employeesWithSalary;
    if(employees.length === 0){
      toast('⚠️ Nenhum funcionário ativo com salário > 0. Configure salários primeiro.','warning'); return;
    }
    const totalEsperado = employees.reduce((s,u)=>s+Number(u.monthly_salary||0),0);
    if(!await showConfirm({
      title:`Gerar Folha de ${payrollMonth}`,
      message:`Vai criar ${employees.length} gasto(s) Pendente(s) — total ${fmt(totalEsperado)}.\n\nFuncionários já com folha desse mês serão pulados (idempotente).\n\nContinuar?`,
      confirmText:'Gerar Folha',confirmColor:'#7C3AED'
    })) return;
    payrollRef.current = true;
    setPayrollInflight(true);
    try{
      const {data, error} = await sb.rpc('generate_monthly_payroll_v1',{p_month: payrollMonth});
      if(error){
        toast('❌ '+error.message,'error');
        if(typeof Sentry!=='undefined') Sentry.captureException(new Error(error.message),{extra:{context:'gerarFolhaMes_v216',month:payrollMonth}});
        return;
      }
      const r = data || {};
      if(r.success === false){
        toast('❌ '+(r.errorMessage||r.errorCode),'error'); return;
      }
      toast(`✅ ${r.created} pendentes criados · ${fmt(r.total_value)} · ${r.skipped_already_exists||0} já existiam`,'success');
      // Refetch gastos do mês pra mostrar
      try{
        const {data: novos} = await sb.from('gastos').select('*').eq('category','Folha de Pagamento').gte('date', payrollMonth+'-01').is('deleted_at',null);
        if(novos && Array.isArray(novos)){
          setGastos(prev=>{
            const existingIds = new Set(prev.map(x=>x.id));
            const adicionar = novos.filter(n=>!existingIds.has(n.id));
            return [...prev, ...adicionar];
          });
        }
      }catch(e){
        console.warn('[v224.71 Gastos.jsx:580] catch suprimido:', e);
        if(window.Sentry) window.Sentry.captureException(e,{tags:{wave:'v224.71',file:'Gastos.jsx',line:580}});
      }
    }catch(e){
      toast('❌ Erro inesperado','error');
      if(typeof Sentry!=='undefined') Sentry.captureException(e,{extra:{context:'gerarFolhaMes_v216'}});
    }finally{
      payrollRef.current = false;
      setPayrollInflight(false);
    }
  }

  // ─── RENDER ────────────────────────────────────────────────────────
  const maxBar = Math.max(...ultimos6Meses.map(x=>x.val), 1);

  // [Wave Gastos FULL 2026-05-18 v223.22] Bloco agregado refs+fail-loud — 6 widgets gastos/
  // regra_estender_bloco_refs_fail_loud + regra_validacao_helpers_runtime_quando_ordem_scripts_uncertain.
  const _DashboardTab = window.ZNX?.widgets?.gastos?.DashboardTab;
  const _FolhaTab = window.ZNX?.widgets?.gastos?.FolhaTab;
  const _LancamentosTab = window.ZNX?.widgets?.gastos?.LancamentosTab;
  const _GastoFormModal = window.ZNX?.widgets?.gastos?.GastoFormModal;
  const _PagarModal = window.ZNX?.widgets?.gastos?.PagarModal;
  const _ReceiptListModal = window.ZNX?.widgets?.gastos?.ReceiptListModal;
  const _missingWidgets = [];
  if (!_DashboardTab) _missingWidgets.push('DashboardTab');
  if (!_FolhaTab) _missingWidgets.push('FolhaTab');
  if (!_LancamentosTab) _missingWidgets.push('LancamentosTab');
  if (!_GastoFormModal) _missingWidgets.push('GastoFormModal');
  if (!_PagarModal) _missingWidgets.push('PagarModal');
  if (!_ReceiptListModal) _missingWidgets.push('ReceiptListModal');
  if (_missingWidgets.length > 0) {
    if (typeof window.znxCaptureRpcError === 'function') {
      window.znxCaptureRpcError('Gastos_widgets_missing', {
        missing: _missingWidgets,
        hint: 'Verificar js/components/widgets/gastos/*.jsx carregaram em index.html'
      });
    }
    console.error('[Gastos] widgets faltando:', _missingWidgets);
  }

  return(
    <div>
      <div className="page-header">
        <div className="page-title">💸 Gestão de Gastos</div>
        {tab==='lancamentos' && (
          <div style={{display:'flex',gap:10,alignItems:'center',flexWrap:'wrap'}}>
            <input type="search" value={searchTerm} onChange={e=>setSearchTerm(e.target.value)} placeholder="🔍 Buscar descrição/beneficiário..." style={{padding:'6px 10px',border:'1px solid #D1D5DB',borderRadius:6,fontSize:13,minWidth:200}}/>
            <input type="month" value={filter.month} onChange={e=>setFilter(f=>({...f,month:e.target.value}))} style={{width:150}}/>
            <SmartSelect width={170} value={filter.category} onChange={val=>setFilter(f=>({...f,category:val}))} placeholder="Todas categorias" options={[{value:'',label:'Todas categorias'},...CATEGORIAS_V216.map(c=>({value:c,label:c}))]}/>
            <SmartSelect width={130} value={filter.status} onChange={val=>setFilter(f=>({...f,status:val}))} placeholder="Todos status" options={[{value:'',label:'Todos status'},...['Pendente','Parcial','Pago','Cancelado'].map(x=>({value:x,label:x}))]}/>
            <SmartSelect width={130} value={filter.method} onChange={val=>setFilter(f=>({...f,method:val}))} placeholder="Todos métodos" options={[{value:'',label:'Todos métodos'},...METODOS.map(x=>({value:x,label:x}))]}/>
            <button onClick={()=>setFilter7d(v=>!v)} style={{padding:'6px 12px',border:'1px solid '+(filter7d?'#DC2626':'#D1D5DB'),borderRadius:6,fontSize:12,fontWeight:filter7d?700:500,background:filter7d?'#FEE2E2':'#fff',color:filter7d?'#991B1B':'#6B7280',cursor:'pointer'}} title="Mostrar só gastos vencendo em ≤7 dias">⚠️ 7d</button>
            {isAdmin && <button className="btn-gold" onClick={openNew} style={{display:'flex',alignItems:'center',gap:6}}><Icon n="plus" size={14}/>Novo Gasto</button>}
          </div>
        )}
      </div>

      {/* TABS */}
      <div style={{display:'flex',gap:4,marginBottom:20,borderBottom:'2px solid #F3F4F6'}}>
        {[
          ['lancamentos','📋 Lançamentos'],
          ['folha','💰 Folha de Pagamento'],
          ['dashboard','📊 Dashboard']
        ].map(([id,label])=>(
          <button key={id} onClick={()=>setTab(id)} style={{padding:'9px 18px',borderRadius:'6px 6px 0 0',fontSize:13,fontWeight:tab===id?700:500,background:tab===id?'#FFFFFF':'transparent',border:tab===id?'2px solid #F3F4F6':'2px solid transparent',borderBottom:tab===id?'2px solid #FFFFFF':'none',color:tab===id?'#2563EB':'#6B7280',marginBottom:tab===id?-2:0,cursor:'pointer'}}>{label}</button>
        ))}
      </div>

      {/* [Wave Gastos FULL v223.22] ABA 1 LANÇAMENTOS → LancamentosTab widget */}
      {tab==='lancamentos' && (_LancamentosTab
        ? <_LancamentosTab
            stats={{totalMes, totalPago, totalPendente, totalFolha}}
            data={{filtered, paymentsByGasto, isAdmin, CAT_COLORS}}
            actions={{onPay: openPayModal, onRevert: reverterPagamento, onReceipt: openReceiptOrList, onEdit: openEdit, onRemove: remove}}
            helpers={{fmt, fmtDate, today}}/>
        : <div style={{padding:12,color:'#DC2626'}}>⚠️ LancamentosTab widget não carregou.</div>)}

      {/* [Wave Gastos FULL v223.22] ABA 2 FOLHA → FolhaTab widget */}
      {tab==='folha' && (_FolhaTab
        ? <_FolhaTab
            period={{payrollMonth, setPayrollMonth}}
            stats={{folhaTotal, folhaPagoMes, employeesWithSalary}}
            payroll={{gerarFolhaMes, payrollInflight, bulkPayFolhaMes, bulkPayInflight}}
            data={{allUsers, folhaStats, paymentsByGasto, isAdmin}}
            actions={{
              onSalaryEdit: (u)=>{ setSalaryEditUser(u); setSalaryEditValue(String(u.monthly_salary||0)); },
              onPay: openPayModal,
              onReceipt: openReceiptOrList
            }}
            helpers={{fmt}}/>
        : <div style={{padding:12,color:'#DC2626'}}>⚠️ FolhaTab widget não carregou.</div>)}

      {/* [Wave Gastos FULL v223.22] ABA 3 DASHBOARD → DashboardTab widget */}
      {tab==='dashboard' && (_DashboardTab
        ? <_DashboardTab
            totalMes={totalMes} mesAnteriorVal={mesAnteriorVal} variacaoVsMesAnt={variacaoVsMesAnt}
            porCategoria={porCategoria} totalPendente={totalPendente}
            filter={filter} filtered={filtered}
            ultimos6Meses={ultimos6Meses} maxBar={maxBar} CAT_COLORS={CAT_COLORS}
            top5Beneficiarios={top5Beneficiarios} fmt={fmt}/>
        : <div style={{padding:12,color:'#DC2626'}}>⚠️ DashboardTab widget não carregou.</div>)}

      {/* [Wave Gastos FULL v223.22] MODAL FORM → GastoFormModal widget */}
      {modal==='form' && _GastoFormModal && (
        <_GastoFormModal
          form={{state: form, setState: setForm, editId}}
          config={{categories: CATEGORIAS_V216, methods: METODOS, recurrences: RECORRENCIAS, allUsers}}
          actions={{onSave: save, onCancel: () => setModal(null), onBeneficiaryChange: onBeneficiaryUserChange}}
          savingRef={savingRef}
          fmt={fmt}/>
      )}
      {modal==='form' && !_GastoFormModal && (
        <div style={{padding:12,color:'#DC2626'}}>⚠️ GastoFormModal widget não carregou.</div>
      )}

      {/* [Wave Gastos FULL v223.22] MODAL PAGAR → PagarModal widget */}
      {payModal && _PagarModal && (
        <_PagarModal
          payModal={payModal} setPayModal={setPayModal}
          payForm={payForm} setPayForm={setPayForm}
          payInflight={payInflight} confirmPayment={confirmPayment}
          METODOS={METODOS} fmt={fmt}/>
      )}
      {payModal && !_PagarModal && (
        <div style={{padding:12,color:'#DC2626'}}>⚠️ PagarModal widget não carregou.</div>
      )}

      {/* [Wave Gastos FULL v223.22] MODAL LISTA RECIBOS → ReceiptListModal widget */}
      {receiptListModal && _ReceiptListModal && (
        <_ReceiptListModal
          receiptListModal={receiptListModal}
          setReceiptListModal={setReceiptListModal}
          gerarRecibo={gerarRecibo}
          fmt={fmt} fmtDate={fmtDate}/>
      )}
      {receiptListModal && !_ReceiptListModal && (
        <div style={{padding:12,color:'#DC2626'}}>⚠️ ReceiptListModal widget não carregou.</div>
      )}

      {/* ═══ MODAL EDITAR SALÁRIO ═══ */}
      {salaryEditUser && (
        <Modal title={`Salário de ${salaryEditUser.name}`} onClose={()=>{setSalaryEditUser(null);setSalaryEditValue('');}}>
          <div className="form-grid">
            <div className="form-group full">
              <label>Salário mensal base (R$)</label>
              <input type="number" step="0.01" value={salaryEditValue} onChange={e=>setSalaryEditValue(e.target.value)} placeholder="0.00" autoFocus/>
              <div style={{fontSize:11,color:'#6B7280',marginTop:6}}>Usado pra auto-gerar folha pendente do mês. Atual: {fmt(Number(salaryEditUser.monthly_salary||0))}</div>
            </div>
          </div>
          <div style={{display:'flex',gap:10,marginTop:20,justifyContent:'flex-end'}}>
            <button className="btn-outline" onClick={()=>{setSalaryEditUser(null);setSalaryEditValue('');}}>Cancelar</button>
            <button className="btn-gold" onClick={saveSalary}>Salvar</button>
          </div>
        </Modal>
      )}
    </div>
  );
}

  window.ZNX = window.ZNX || {};
  window.ZNX.components = window.ZNX.components || {};
  window.ZNX.components.Gastos = Gastos;
  window.Gastos = Gastos;

  window.ZNX.refactor_phase_5_loaded = window.ZNX.refactor_phase_5_loaded || {};
  window.ZNX.refactor_phase_5_loaded.Gastos = true;

})();
