/* global React, Icon, Button, Badge, Card, Drawer, Field, EmptyState, PageHead, FooterPager,
   brl, numBr, fmtDate, tmPode, tmMotivoNegado,
   tmFolha, colabPrevia, colabMsgErro, colabRegime, colabVinculo,
   colabDiaPago, colabDiaPagoLabel, colabTrabalhaNoDia, colabDiasLabel,
   colabIso, colabAddDias, colabDiaSemanaIso, colabPeriodoPadrao, colabHorasEntre,
   useColaboradoresList, useContasBancarias, LancamentoForm,
   COLAB_TIPOS_DIA, COLAB_DIAS_ISO */

// ─────────────────────────────────────────────────────────────────────────────
//  Colaboradores · apontamento e fechamento
//
//  ⚠️ RELÓGIO E DEPENDÊNCIA. `colabIso(new Date())` aparece aqui SÓ dentro de
//     inicializador de useState — roda uma vez. Todo efeito depende de STRING de
//     data (semanaIni, inicio, fim) e de contadores, nunca de um Date novo. Foi
//     um `end: new Date()` num array de dependência que produziu 143,6 milhões
//     de requisições em 49 dias.
// ─────────────────────────────────────────────────────────────────────────────

function segundaDaSemana(iso) {
  let d = iso;
  for (let i = 0; i < 7 && colabDiaSemanaIso(d) !== 1; i++) d = colabAddDias(d, -1);
  return d;
}
const fmtH = (n) => numBr(n).toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 2 }) + ' h';

// Quanto AQUELE dia entra na conta, e por quê. Declarado no escopo do módulo,
// nunca dentro de outro componente: componente declarado dentro de componente
// vira um TIPO novo a cada render, o React desmonta a subárvore e o input perde
// o foco a cada tecla (já mordeu no formulário de pedido de compra).
function PagaCelula({ d, jornada }) {
  const pago = colabDiaPago(d.tipo, d.horas, jornada);
  const nota = colabDiaPagoLabel(d.tipo, d.horas, jornada);
  if (d.tipo === 'falta') return <span className="cl-paga zero" title="falta não paga">0,0 h</span>;
  return (
    <span className={'cl-paga' + (nota ? ' bonus' : '')} title={nota || 'horas lançadas'}>
      {fmtH(pago.horas)}
      {nota ? <small>{nota}</small> : null}
    </span>
  );
}

// Seletor de pessoa reaproveitado pelas duas telas.
function SeletorPessoa({ rows, valor, onChange }) {
  return (
    <select className="input" style={{ minWidth: 240 }} value={valor || ''}
      onChange={e => onChange(e.target.value)}>
      <option value="">Escolha a pessoa…</option>
      {rows.map(c => (
        <option key={c.id} value={c.id}>
          {(c.apelido || c.nome) + (c.situacao === 'Arquivado' ? ' (arquivado)' : '')}
        </option>
      ))}
    </select>
  );
}

function FaixaPessoa({ c }) {
  const reg = colabRegime(c.regime);
  const ciclo = c.ciclo === 'mensal'
    ? 'mensal, dia ' + (c.ciclo_dia || '—')
    : (c.ciclo || '—') + (c.ciclo_dia ? ', ' + COLAB_DIAS_ISO[c.ciclo_dia].toLowerCase() : '');
  return (
    <div className="cl-pessoa">
      <div className="cl-av">{(c.apelido || c.nome || '?').slice(0, 2).toUpperCase()}</div>
      <div style={{ minWidth: 0 }}>
        <div className="cl-nm">{c.nome}</div>
        <div className="cl-deal">
          {colabVinculo(c.vinculo)} · <b>{reg ? reg.lbl.toLowerCase() : '—'}</b>
          {c.valor_regime != null
            ? <> · <b>R$ {brl(c.valor_regime)}</b> <span className="cl-un">{reg ? reg.un.replace('R$ ', '') : ''}</span></>
            : <> · <b>valor variável</b></>}
          {c.jornada_horas ? <> · jornada <b>{fmtH(c.jornada_horas)}/dia</b></> : null}
          {' '}· <b>{colabDiasLabel(c)}</b>
          {' '}· pagamento <b>{ciclo}</b>
        </div>
      </div>
      <div className="cl-papeis">
        <span className={'cl-chip' + (c.usuario_id ? ' on' : '')}>{c.usuario_id ? 'tem login' : 'sem login'}</span>
        <span className={'cl-chip' + (c.vendedor_id ? ' on' : '')}>{c.vendedor_id ? 'vende' : 'não vende'}</span>
      </div>
    </div>
  );
}

// ═══ TELA 1 · Apontamento — a semana numa grade ═════════════════════════════
function ColaboradoresApontamento({ onNav }) {
  const lista = useColaboradoresList([]);
  const pessoas = React.useMemo(
    () => [...lista.rows].sort((a, b) => String(a.nome || '').localeCompare(String(b.nome || ''))),
    [lista.rows]
  );

  const [hoje] = React.useState(() => colabIso(new Date()));       // ⚠️ uma vez só
  const [colabId, setColabId]   = React.useState('');
  const [semanaIni, setSemana]  = React.useState(() => segundaDaSemana(colabIso(new Date())));
  const [linhas, setLinhas]     = React.useState([]);
  const [atrasados, setAtras]   = React.useState([]);
  const [lancs, setLancs]       = React.useState([]);
  const [fechs, setFechs]       = React.useState([]);
  const [carregando, setCarreg] = React.useState(false);
  const [erro, setErro]         = React.useState('');
  const [recarga, setRecarga]   = React.useState(0);
  const [lancOpen, setLancOpen] = React.useState(false);
  const [fecharOpen, setFechar] = React.useState(false);
  const [verExtras, setExtras]  = React.useState(false);
  // Estado de gravação POR DIA (a data é a chave estável; índice muda quando a
  // grade filtra os dias do combinado).
  const [estado, setEstado]     = React.useState({});   // pendente|salvando|salvo|erro
  const [erroDia, setErroDia]   = React.useState({});

  const colab = pessoas.filter(p => p.id === colabId)[0] || null;
  const semanaFim = colabAddDias(semanaIni, 6);
  const podeEditar = tmPode('colaboradores', 'editar');
  const podeCriar  = tmPode('colaboradores', 'criar');

  // ── Gravação: já era no onBlur; o que faltava era DIZER que gravou ────────
  // Três mecanismos, e cada um cobre um buraco do outro:
  //   1. onBlur grava na hora (o que já existia);
  //   2. onChange agenda uma gravação em 800 ms — assim fechar a aba com o
  //      cursor DENTRO do campo perde no máximo a última fração de segundo, em
  //      vez do campo inteiro (o onBlur não dispara quando a aba fecha);
  //   3. visibilitychange/pagehide e a desmontagem descarregam o que estiver
  //      pendente — é o que o `beforeunload` não faz (ele avisa, não salva).
  const pendentes = React.useRef({});   // { dataIso: { timer, snap } }

  const gravarSnap = React.useCallback(async (dataIso, snap) => {
    if (!snap || snap.fechamento_id) return;
    setEstado(e => ({ ...e, [dataIso]: 'salvando' }));
    const res = await tmFolha.salvarDia(snap);
    if (res && res.error) {
      setEstado(e => ({ ...e, [dataIso]: 'erro' }));
      setErroDia(e => ({ ...e, [dataIso]: colabMsgErro(res.error) }));
      return;
    }
    if (res && res.data) {
      setLinhas(ls => ls.map(x => x.data === dataIso ? { ...x, id: res.data.id } : x));
    }
    setEstado(e => ({ ...e, [dataIso]: 'salvo' }));
    setErroDia(e => { const n = { ...e }; delete n[dataIso]; return n; });
  }, []);

  const vazia = (l) => !l.entrada && !l.saida && numBr(l.horas) === 0
    && !String(l.observacao || '').trim() && l.tipo === 'trabalho';

  const enfileirar = React.useCallback((linha, imediato) => {
    const dataIso = linha.data;
    const p = pendentes.current[dataIso];
    if (p && p.timer) clearTimeout(p.timer);
    if (linha.fechamento_id) { delete pendentes.current[dataIso]; return; }
    // Linha em branco sem registro não vira linha no banco — o dia só existe
    // quando alguém escreveu alguma coisa nele.
    if (vazia(linha) && !linha.id) { delete pendentes.current[dataIso]; return; }
    // O snapshot carrega o colaborador_id: se ela trocar de pessoa com um
    // timer no ar, o que grava é o dono certo do dado.
    const snap = { ...linha, colaborador_id: linha.colaborador_id || colabId };
    if (imediato) {
      delete pendentes.current[dataIso];
      gravarSnap(dataIso, snap);
      return;
    }
    setEstado(e => ({ ...e, [dataIso]: 'pendente' }));
    pendentes.current[dataIso] = {
      snap,
      timer: setTimeout(() => { delete pendentes.current[dataIso]; gravarSnap(dataIso, snap); }, 800),
    };
  }, [colabId, gravarSnap]);

  // Descarrega tudo que estiver pendente. Ref para o listener não envelhecer.
  const descarregarRef = React.useRef(() => {});
  descarregarRef.current = () => {
    Object.keys(pendentes.current).forEach(d => {
      const p = pendentes.current[d];
      if (p && p.timer) clearTimeout(p.timer);
      delete pendentes.current[d];
      if (p) gravarSnap(d, p.snap);
    });
  };
  React.useEffect(() => {
    const f = () => descarregarRef.current();
    const onVis = () => { if (document.visibilityState === 'hidden') f(); };
    document.addEventListener('visibilitychange', onVis);
    window.addEventListener('pagehide', f);
    return () => {
      document.removeEventListener('visibilitychange', onVis);
      window.removeEventListener('pagehide', f);
      f();                                  // sair da tela também grava
    };
  }, []);

  // Efeito com deps ESTÁVEIS: id + duas strings de data + contador.
  React.useEffect(() => {
    Object.keys(pendentes.current).forEach(d => {
      if (pendentes.current[d].timer) clearTimeout(pendentes.current[d].timer);
      delete pendentes.current[d];
    });
    setEstado({}); setErroDia({}); setExtras(false);
    if (!colabId) { setLinhas([]); setAtras([]); setLancs([]); setFechs([]); return; }
    let vivo = true;
    setCarreg(true);
    Promise.all([
      tmFolha.dias(colabId, semanaIni, semanaFim),
      tmFolha.diasAbertos(colabId, colabAddDias(semanaIni, -1)),
      tmFolha.lancamentosAbertos(colabId, semanaFim),
      tmFolha.fechamentos(colabId),
    ]).then(([dias, atras, ls, fs]) => {
      if (!vivo) return;
      const porData = {};
      (dias || []).forEach(d => { porData[String(d.data).slice(0, 10)] = d; });
      const grade = [];
      for (let i = 0; i < 7; i++) {
        const data = colabAddDias(semanaIni, i);
        const ex = porData[data];
        grade.push(ex
          ? { ...ex, data, horas: ex.horas == null ? '' : String(ex.horas).replace('.', ',') }
          : { id: null, colaborador_id: colabId, data, entrada: '', saida: '', horas: '',
              tipo: 'trabalho', observacao: '', fechamento_id: null });
      }
      setLinhas(grade); setAtras(atras || []); setLancs(ls || []); setFechs((fs || []).slice(0, 5));
      setCarreg(false);
    }).catch(e => { if (vivo) { setErro(String(e && e.message || e)); setCarreg(false); } });
    return () => { vivo = false; };
  }, [colabId, semanaIni, semanaFim, recarga]);

  const setCampo = (dataIso, campo, valor) => {
    setLinhas(ls => ls.map(l => {
      if (l.data !== dataIso) return l;
      const nova = { ...l, [campo]: valor };
      // Horário preenchido calcula as horas; digitar as horas por cima também
      // vale (turno partido não cabe num par entrada/saída).
      if (campo === 'entrada' || campo === 'saida') {
        const h = colabHorasEntre(campo === 'entrada' ? valor : nova.entrada,
                                  campo === 'saida'   ? valor : nova.saida);
        if (h > 0) nova.horas = String(h).replace('.', ',');
      }
      enfileirar(nova, false);
      return nova;
    }));
  };
  const gravarAgora = (dataIso) => {
    const l = linhas.filter(x => x.data === dataIso)[0];
    if (l) enfileirar(l, true);
  };

  const apagar = async (dataIso) => {
    const l = linhas.filter(x => x.data === dataIso)[0];
    if (!l || !l.id) return;
    if (!confirm('Apagar o lançamento de ' + fmtDate(l.data) + '?')) return;
    const res = await tmFolha.apagarDia(l.id);
    if (res && res.error) { setErro(colabMsgErro(res.error)); return; }
    setRecarga(r => r + 1);
  };

  // ── Quais dias a grade mostra ────────────────────────────────────────────
  // Os dias do combinado, mais qualquer dia que JÁ tenha lançamento (um sábado
  // trabalhado não some da tela porque não está no acordo), mais os demais
  // quando ela pedir. Sem obrigar a mexer no cadastro por uma exceção.
  const linhasVisiveis = linhas.filter(l =>
    verExtras || colabTrabalhaNoDia(colab, l.data) || l.id);
  const escondidos = linhas.length - linhasVisiveis.length;

  const jornada = colab && colab.jornada_horas;
  const totalPago = linhasVisiveis.reduce(
    (a, l) => a + colabDiaPago(l.tipo, l.horas, jornada).horas, 0);
  const esperado = colab && colab.jornada_horas
    ? linhas.filter(l => colabTrabalhaNoDia(colab, l.data)).length * numBr(colab.jornada_horas)
    : null;
  const horasAtrasadas = atrasados.reduce((a, d) => a + colabDiaPago(d.tipo, d.horas, jornada).horas, 0);

  // Três contagens separadas, não duas: dizer "salvando…" enquanto o dado só
  // está PENDENTE é uma mentirinha, e o ponto deste indicador é ser confiável.
  const nGravando  = Object.keys(estado).filter(d => estado[d] === 'salvando').length;
  const nPendentes = Object.keys(estado).filter(d => estado[d] === 'pendente').length;
  const nErros     = Object.keys(estado).filter(d => estado[d] === 'erro').length;
  const nSalvos    = Object.keys(estado).filter(d => estado[d] === 'salvo').length;

  return (
    <div className="canvas">
      <PageHead title="Pessoas · Apontamento" sub="quem veio, que horas, e quanto isso vale"
        right={<>
          <SeletorPessoa rows={pessoas} valor={colabId} onChange={setColabId} />
          <Button variant="ghost" icon="users" onClick={() => onNav && onNav('colaboradores')}>Cadastro</Button>
        </>} />

      {!colabId ? (
        <Card padded>
          <EmptyState icon="user-round-search"
            title={pessoas.length ? 'Escolha a pessoa' : 'Nenhuma pessoa cadastrada'}
            msg={pessoas.length
              ? 'A grade é de uma pessoa por vez — é assim que o pagamento é feito.'
              : 'Cadastre quem recebe pela folha em Pessoas › Cadastro.'}
            action={pessoas.length ? null :
              <Button variant="primary" icon="plus" onClick={() => onNav && onNav('colaboradores')}>
                Ir para o cadastro
              </Button>} />
        </Card>
      ) : (
        <>
          <FaixaPessoa c={colab} />

          <div className="cl-weeknav">
            <Button variant="ghost" icon="chevron-left" onClick={() => setSemana(s => colabAddDias(s, -7))} />
            <span className="cl-weeklbl">{fmtDate(semanaIni)} – {fmtDate(semanaFim)}</span>
            <Button variant="ghost" icon="chevron-right" onClick={() => setSemana(s => colabAddDias(s, 7))} />
            <Button variant="ghost" onClick={() => setSemana(segundaDaSemana(hoje))}>Semana atual</Button>
            {carregando && <span className="cl-hint">carregando…</span>}
          </div>

          {atrasados.length > 0 && (
            <div className="cl-aviso warn">
              <Icon name="alert-triangle" size={15} />
              <span>
                <b>{atrasados.length === 1
                  ? fmtDate(atrasados[0].data) + ' · ' + fmtH(colabDiaPago(atrasados[0].tipo, atrasados[0].horas, jornada).horas)
                  : atrasados.length + ' dias · ' + fmtH(horasAtrasadas)}</b>{' '}
                de período anterior ainda sem pagamento. Entra no próximo fechamento — não some.
              </span>
            </div>
          )}
          {erro ? <div className="cl-erro">{erro}</div> : null}

          <div className="cl-grid2">
            <div className="cl-card">
              <div className="cl-cardhead">
                <h4>Semana de {fmtDate(semanaIni)} a {fmtDate(semanaFim)}</h4>
                <span className={'cl-salvo ' + (nErros ? 'erro' : (nGravando || nPendentes) ? 'pend' : nSalvos ? 'ok' : 'idle')}>
                  {nErros ? <><Icon name="alert-circle" size={13} /> {nErros} linha(s) com erro</>
                    : nGravando ? <><Icon name="loader" size={13} /> salvando…</>
                    : nPendentes ? <><Icon name="clock" size={13} /> ainda não salvo</>
                    : nSalvos ? <><Icon name="check" size={13} /> tudo salvo</>
                    : <>grava sozinho — não precisa de botão</>}
                </span>
              </div>
              <table className="tbl cl-tbl">
                <thead><tr>
                  <th style={{ width: 112 }}>Dia</th>
                  <th style={{ width: 128 }}>Tipo</th>
                  <th style={{ width: 84 }}>Entrada</th>
                  <th style={{ width: 84 }}>Saída</th>
                  <th style={{ width: 82 }} className="right">Horas</th>
                  <th style={{ width: 148 }} className="right">Paga</th>
                  <th>Observação</th>
                  <th style={{ width: 62 }}></th>
                </tr></thead>
                <tbody>
                  {linhasVisiveis.map(l => {
                    const dsi   = colabDiaSemanaIso(l.data);
                    const fora  = !colabTrabalhaNoDia(colab, l.data);
                    const trav  = !!l.fechamento_id;
                    const pago  = colabDiaPago(l.tipo, l.horas, jornada);
                    const dif   = (colab.jornada_horas && l.tipo === 'trabalho' && numBr(l.horas) > 0)
                      ? Math.round((numBr(l.horas) - numBr(colab.jornada_horas)) * 100) / 100 : null;
                    const ro    = trav || !podeEditar;
                    const st    = estado[l.data];
                    return (
                      <tr key={l.data} className={(fora ? 'cl-fora' : '') + (trav ? ' cl-trav' : '')}>
                        <td className="cl-dia">
                          {COLAB_DIAS_ISO[dsi].slice(0, 3).toLowerCase()} <span>{fmtDate(l.data).slice(0, 5)}</span>
                          {trav && <Icon name="lock" size={11} className="muted" style={{ marginLeft: 6 }} />}
                          {fora && !trav && <span className="cl-fora-tag" title="Fora dos dias do combinado">extra</span>}
                        </td>
                        <td>
                          <select className="input cl-in" value={l.tipo} disabled={ro}
                            onChange={e => { setCampo(l.data, 'tipo', e.target.value); }}
                            onBlur={() => gravarAgora(l.data)}>
                            {COLAB_TIPOS_DIA.map(t => <option key={t.v} value={t.v}>{t.lbl}</option>)}
                          </select>
                        </td>
                        <td><input className="input cl-in" type="time" value={l.entrada || ''} disabled={ro}
                          onChange={e => setCampo(l.data, 'entrada', e.target.value)} onBlur={() => gravarAgora(l.data)} /></td>
                        <td><input className="input cl-in" type="time" value={l.saida || ''} disabled={ro}
                          onChange={e => setCampo(l.data, 'saida', e.target.value)} onBlur={() => gravarAgora(l.data)} /></td>
                        <td className="right">
                          <input className="input cl-in right" value={l.horas} disabled={ro} placeholder="0,0"
                            onChange={e => setCampo(l.data, 'horas', e.target.value)} onBlur={() => gravarAgora(l.data)} />
                        </td>
                        <td className="right">
                          {pago.horas > 0 || l.tipo === 'falta'
                            ? <PagaCelula d={l} jornada={jornada} />
                            : <span className="cl-dif zero">—</span>}
                          {dif != null && dif !== 0 && (
                            <div className={'cl-dif ' + (dif < 0 ? 'down' : 'up')}>
                              {(dif > 0 ? '+' : '−') + fmtH(Math.abs(dif)).replace(' h', '')}
                            </div>
                          )}
                        </td>
                        <td>
                          <input className="input cl-in" value={l.observacao || ''} disabled={ro} placeholder=""
                            onChange={e => setCampo(l.data, 'observacao', e.target.value)} onBlur={() => gravarAgora(l.data)} />
                        </td>
                        <td className="right cl-acoes">
                          <span className={'cl-st ' + (st || (l.id ? 'salvo' : 'vazio'))}
                            title={st === 'erro' ? (erroDia[l.data] || 'erro ao salvar')
                              : st === 'pendente' ? 'ainda não salvo'
                              : st === 'salvando' ? 'salvando…'
                              : l.id ? 'salvo' : ''}>
                            {st === 'erro' ? '!' : st === 'pendente' ? '•' : st === 'salvando' ? '…' : l.id ? '✓' : ''}
                          </span>
                          {l.id && !trav && podeEditar && (
                            <Button variant="ghost" size="sm" icon="trash-2" title="Apagar o dia"
                              onClick={() => apagar(l.data)} />
                          )}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              <div className="cl-tfoot">
                <span className="k">Total a pagar na semana</span><span className="v">{fmtH(totalPago)}</span>
                {esperado != null && <>
                  <span className="k">Esperado</span><span className="v">{fmtH(esperado)}</span>
                  <span className="k">Diferença</span>
                  <span className="v" style={{ color: totalPago === esperado ? 'var(--tm-fg-2)' : 'var(--tm-warning)' }}>
                    {totalPago === esperado ? fmtH(0)
                      : (totalPago > esperado ? '+' : '−') + fmtH(Math.abs(totalPago - esperado))}
                  </span>
                </>}
                {escondidos > 0 && !verExtras && (
                  <a className="cl-link" style={{ marginLeft: 'auto' }} onClick={() => setExtras(true)}>
                    + incluir outro dia ({escondidos} fora do combinado)
                  </a>
                )}
                {verExtras && escondidos === 0 && (
                  <a className="cl-link" style={{ marginLeft: 'auto' }} onClick={() => setExtras(false)}>
                    mostrar só os dias do combinado
                  </a>
                )}
                {!podeEditar && <span className="cl-hint" style={{ marginLeft: 'auto' }}>
                  {tmMotivoNegado('colaboradores', 'editar')}
                </span>}
              </div>
              {nErros > 0 && (
                <div className="cl-erro" style={{ margin: 12 }}>
                  {Object.keys(erroDia).map(d => (
                    <div key={d}>{fmtDate(d)}: {erroDia[d]}</div>
                  ))}
                </div>
              )}
            </div>

            <div className="cl-side">
              <div className="cl-card">
                <div className="cl-cardhead">
                  <h4>Vale, bônus e desconto</h4>
                  <Button variant="ghost" size="sm" icon="plus" onClick={() => setLancOpen(true)}
                    disabled={!podeCriar} title={podeCriar ? '' : tmMotivoNegado('colaboradores', 'criar')}>
                    Lançar
                  </Button>
                </div>
                {lancs.length === 0 ? (
                  <div className="cl-nota">Nada em aberto. O que for lançado aqui entra no próximo fechamento.</div>
                ) : lancs.map(l => (
                  <div key={l.id} className="cl-lanc">
                    <span className={'cl-ic ' + l.tipo}>{l.tipo === 'vale' ? 'V' : l.tipo === 'bonus' ? 'B' : 'D'}</span>
                    <div style={{ minWidth: 0 }}>
                      <div className="t">{l.tipo === 'vale' ? 'Vale' : l.tipo === 'bonus' ? 'Bônus' : 'Desconto'} · {fmtDate(l.data)}</div>
                      <div className="s">{l.motivo || (l.conta_pagar_id ? 'título pago' : 'sem motivo anotado')}</div>
                    </div>
                    <div className={'v ' + (l.tipo === 'bonus' ? 'plus' : 'minus')}>
                      {(l.tipo === 'bonus' ? '+ ' : '− ') + brl(l.valor)}
                    </div>
                  </div>
                ))}
              </div>

              <div className="cl-card">
                <div className="cl-cardhead"><h4>Últimos fechamentos</h4></div>
                {fechs.length === 0 ? (
                  <div className="cl-nota">Nenhum fechamento ainda para esta pessoa.</div>
                ) : fechs.map(f => (
                  <div key={f.id} className="cl-lanc">
                    <span className="cl-ic ok">{f.status === 'cancelado' ? '✕' : '✓'}</span>
                    <div style={{ minWidth: 0 }}>
                      <div className="t">{fmtDate(f.periodo_inicio)} – {fmtDate(f.periodo_fim)}</div>
                      <div className="s">
                        {fmtH(f.horas_total)} · {f.status === 'cancelado' ? 'desfeito' : 'aplicado ' + fmtDate(f.aplicado_em)}
                      </div>
                    </div>
                    <div className="v">{brl(f.liquido)}</div>
                  </div>
                ))}
                <div className="cl-nota" style={{ borderTop: '1px solid var(--tm-line-1)' }}>
                  <a className="cl-link" onClick={() => onNav && onNav('colaboradores/fechamentos')}>
                    Ver todos os fechamentos →
                  </a>
                </div>
              </div>
            </div>
          </div>

          <div className="cl-actionbar">
            <Button variant="primary" icon="calendar-check" onClick={() => setFechar(true)}
              disabled={!podeCriar} title={podeCriar ? '' : tmMotivoNegado('colaboradores', 'criar')}>
              Fechar período
            </Button>
            <Button icon="banknote" onClick={() => setLancOpen(true)} disabled={!podeCriar}>Lançar vale</Button>
            <span className="cl-why">
              <b>Lançar e fechar são etapas separadas.</b> Durante a semana você só lança —
              cada campo grava sozinho, e dá para voltar amanhã. Fechar é o passo da sexta:
              gera o título no Contas a Pagar e trava os dias.
            </span>
          </div>
        </>
      )}

      {lancOpen && colab && (
        <LancamentoForm colaborador={colab} onClose={() => setLancOpen(false)}
          onFeito={() => { setLancOpen(false); setRecarga(r => r + 1); }} />
      )}
      {fecharOpen && colab && (
        <FechamentoDrawer colaborador={colab} onClose={() => setFechar(false)}
          onAplicado={() => setRecarga(r => r + 1)} />
      )}
    </div>
  );
}

// ═══ O fechamento · prévia item a item, depois o banco recalcula ════════════
function FechamentoDrawer({ colaborador, onClose, onAplicado }) {
  const { contas } = useContasBancarias();
  const [per] = React.useState(() => colabPeriodoPadrao(colaborador));   // ⚠️ uma vez só
  const [inicio, setInicio] = React.useState(per.inicio);
  const [fim, setFim]       = React.useState(per.fim);
  const [dias, setDias]     = React.useState([]);
  const [lancs, setLancs]   = React.useState([]);
  const [prolab, setProlab] = React.useState('');
  const [jaPaguei, setJa]   = React.useState(true);
  const [conta, setConta]   = React.useState('');
  const [dataPg, setDataPg] = React.useState(per.fim);
  const [forma, setForma]   = React.useState('Transferência');
  const [obs, setObs]       = React.useState('');
  const [busy, setBusy]     = React.useState(false);
  const [erro, setErro]     = React.useState('');
  const [feito, setFeito]   = React.useState(null);

  React.useEffect(() => {
    let vivo = true;
    Promise.all([
      tmFolha.diasAbertos(colaborador.id, fim),
      tmFolha.lancamentosAbertos(colaborador.id, fim),
    ]).then(([ds, ls]) => { if (vivo) { setDias(ds || []); setLancs(ls || []); } });
    return () => { vivo = false; };
  }, [colaborador.id, fim]);

  const doPeriodo  = dias.filter(d => String(d.data).slice(0, 10) >= inicio);
  const atrasados  = dias.filter(d => String(d.data).slice(0, 10) <  inicio);
  const previa     = colabPrevia(colaborador, dias, lancs, prolab);
  const reg        = colabRegime(colaborador.regime);
  const precisaProlab = colaborador.regime === 'prolabore' && colaborador.valor_regime == null;

  const aplicar = async () => {
    setErro('');
    if (!inicio || !fim || fim < inicio) { setErro('Confira as datas do período.'); return; }
    if (previa.semValor) { setErro('Pró-labore variável: informe o valor deste período.'); return; }
    if (previa.precisaJornada) {
      setErro('Há folga ou feriado no período e esta pessoa não tem jornada por dia no cadastro — '
        + 'o dia concedido não pode ser valorado. Preencha a jornada em Pessoas › Cadastro e feche de novo.');
      return;
    }
    if (previa.negativo) {
      setErro('Os vales (R$ ' + brl(previa.vales) + ') passam do valor do período (R$ '
        + brl(previa.bruto + previa.bonus - previa.descontos) + '). Ajuste antes de fechar.');
      return;
    }
    if (jaPaguei && !conta) { setErro('Escolha a conta bancária que vai debitar o pagamento.'); return; }
    setBusy(true);
    const res = await tmFolha.fechar({
      colaboradorId: colaborador.id, inicio, fim,
      valorProlabore: precisaProlab ? prolab : null,
      contaId: jaPaguei ? conta : null,
      dataPagamento: jaPaguei ? dataPg : null,
      forma: jaPaguei ? forma : null,
      vencimento: fim, observacoes: obs,
    });
    setBusy(false);
    if (res && res.error) { setErro(colabMsgErro(res.error)); return; }
    setFeito(res.data || {});
    onAplicado && onAplicado();
  };

  // O que o BANCO contou × o que a tela tinha mostrado. Se divergirem, a tela
  // diz — quem manda é o banco, e esconder a diferença seria mentir sobre o
  // número que virou dinheiro.
  const divergiu = feito && !feito.ja_aplicado &&
    (Math.abs(numBr(feito.liquido) - previa.liquido) > 0.005 ||
     Math.abs(numBr(feito.horas_total) - previa.horas) > 0.005);

  return (
    <Drawer open onClose={onClose} width={880}
      eyebrow="Folha" title={'Fechar período — ' + colaborador.nome}
      subtitle={reg ? reg.lbl + (colaborador.valor_regime != null ? ' · R$ ' + brl(colaborador.valor_regime) + ' ' + reg.un.replace('R$ ', '') : ' · valor variável') : ''}
      footer={feito ? (
        <Button variant="primary" icon="check" onClick={onClose}>Fechar</Button>
      ) : (
        <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
          <Button variant="primary" icon="calendar-check" onClick={aplicar} disabled={busy}>
            {busy ? 'Aplicando…' : 'Aplicar fechamento'}
          </Button>
          <Button variant="ghost" onClick={onClose} disabled={busy}>Cancelar</Button>
          <span className="cl-hint">
            Depois de aplicado, os dias ficam travados e a correção entra como lançamento novo.
          </span>
        </div>
      )}>

      {feito ? (
        <div>
          {feito.ja_aplicado ? (
            <div className="cl-aviso warn">
              <Icon name="info" size={15} />
              <span>Este período <b>já estava fechado</b> para esta pessoa. Nenhum título novo
                foi gerado — é a trava de idempotência funcionando.</span>
            </div>
          ) : (
            <>
              <div className="cl-aviso ok">
                <Icon name="check-circle" size={15} />
                <span>Fechamento aplicado. {numBr(feito.liquido) > 0
                  ? <>Título de <b>R$ {brl(feito.liquido)}</b> criado no Contas a Pagar
                      ({feito.status_titulo === 'Pago' ? 'já pago' : 'em aberto'}).</>
                  : <>Líquido zero — nenhum título foi criado, para não poluir o fluxo de caixa
                      com uma linha de R$ 0,00.</>}</span>
              </div>
              <div className="cl-resumo" style={{ marginTop: 16 }}>
                <div className="cl-sum"><span className="k">Horas contadas pelo banco</span><span className="v">{fmtH(feito.horas_total)}</span></div>
                <div className="cl-sum"><span className="k">Dias trabalhados</span><span className="v">{feito.dias_total}</span></div>
                <div className="cl-sum"><span className="k">Bruto</span><span className="v">R$ {brl(feito.bruto)}</span></div>
                <div className="cl-sum"><span className="k">Bônus</span><span className="v">+ R$ {brl(feito.bonus)}</span></div>
                <div className="cl-sum"><span className="k">Descontos</span><span className="v">− R$ {brl(feito.descontos)}</span></div>
                <div className="cl-sum"><span className="k">Vales já pagos</span><span className="v">− R$ {brl(feito.vales)}</span></div>
                <div className="cl-liq"><span className="k">Líquido</span><span className="v"><small>R$</small>{brl(feito.liquido)}</span></div>
              </div>
              {divergiu && (
                <div className="cl-aviso warn" style={{ marginTop: 14 }}>
                  <Icon name="alert-triangle" size={15} />
                  <span>Os números mudaram entre a prévia e a aplicação — a tela mostrava{' '}
                    <b>{fmtH(previa.horas)} / R$ {brl(previa.liquido)}</b>. Vale o que o banco
                    contou, acima. Provavelmente alguém lançou algo enquanto esta tela estava aberta.</span>
                </div>
              )}
            </>
          )}
        </div>
      ) : (
        <>
          <div className="cl-aviso info">
            <Icon name="info" size={15} />
            <span>Isto é a <b>prévia</b>. Nada foi gravado. Ao aplicar, o banco <b>recalcula do
              zero</b> e mostra o que de fato contou.</span>
          </div>

          <div className="field-grid-3" style={{ marginTop: 4 }}>
            <Field label="Início do período" type="date" value={inicio} onChange={setInicio} />
            <Field label="Fim do período" type="date" value={fim} onChange={setFim} />
            {precisaProlab ? (
              <Field label="Valor do pró-labore" value={prolab} onChange={setProlab}
                placeholder="0,00" suffix="R$" hint="Variável — definido a cada fechamento" />
            ) : (
              <Field label="Base do cálculo">
                <div className="input" style={{ display: 'flex', alignItems: 'center', color: 'var(--tm-fg-3)' }}>
                  {reg ? reg.lbl : '—'}{colaborador.valor_regime != null ? ' · R$ ' + brl(colaborador.valor_regime) : ''}
                </div>
              </Field>
            )}
          </div>

          <div className="cl-secname" style={{ marginTop: 18 }}>Dias do período</div>
          {doPeriodo.length === 0 ? (
            <div className="cl-nota">Nenhum dia lançado dentro deste período.</div>
          ) : (
            <table className="tbl cl-tbl">
              <thead><tr>
                <th style={{ width: 130 }}>Dia</th><th style={{ width: 110 }}>Tipo</th>
                <th style={{ width: 150 }}>Horário</th><th style={{ width: 92 }} className="right">Horas</th>
                <th>Observação</th>
              </tr></thead>
              <tbody>
                {doPeriodo.map(d => (
                  <tr key={d.id}>
                    <td className="cl-dia">{COLAB_DIAS_ISO[colabDiaSemanaIso(d.data)].slice(0, 3).toLowerCase()} <span>{fmtDate(d.data)}</span></td>
                    <td>{(COLAB_TIPOS_DIA.filter(t => t.v === d.tipo)[0] || {}).lbl || d.tipo}</td>
                    <td>{d.entrada && d.saida ? String(d.entrada).slice(0, 5) + ' – ' + String(d.saida).slice(0, 5) : '—'}</td>
                    <td className="right">{fmtH(d.horas)}</td>
                    <td className="right"><PagaCelula d={d} jornada={colaborador.jornada_horas} /></td>
                    <td className="muted">{d.observacao || '—'}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}

          {atrasados.length > 0 && (
            <>
              <div className="cl-secname">
                De períodos anteriores <span className="cl-tag">ainda não pago</span>
              </div>
              <table className="tbl cl-tbl cl-past">
                <tbody>
                  {atrasados.map(d => (
                    <tr key={d.id}>
                      <td className="cl-dia" style={{ width: 130 }}>{COLAB_DIAS_ISO[colabDiaSemanaIso(d.data)].slice(0, 3).toLowerCase()} <span>{fmtDate(d.data)}</span></td>
                      <td style={{ width: 110 }}>{(COLAB_TIPOS_DIA.filter(t => t.v === d.tipo)[0] || {}).lbl || d.tipo}</td>
                      <td style={{ width: 140 }}>{d.entrada && d.saida ? String(d.entrada).slice(0, 5) + ' – ' + String(d.saida).slice(0, 5) : '—'}</td>
                      <td className="right" style={{ width: 84 }}>{fmtH(d.horas)}</td>
                      <td className="right" style={{ width: 150 }}><PagaCelula d={d} jornada={colaborador.jornada_horas} /></td>
                      <td className="muted">{d.observacao || '—'}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
              <div className="cl-nota">
                Entram nesta conta de propósito: trabalho feito e ainda não pago não some
                porque foi lançado depois do fechamento anterior.
              </div>
            </>
          )}

          <div className="cl-secname">Lançamentos</div>
          {lancs.length === 0 ? (
            <div className="cl-nota">Nenhum vale, bônus ou desconto em aberto.</div>
          ) : lancs.map(l => (
            <div key={l.id} className="cl-lanc">
              <span className={'cl-ic ' + l.tipo}>{l.tipo === 'vale' ? 'V' : l.tipo === 'bonus' ? 'B' : 'D'}</span>
              <div style={{ minWidth: 0 }}>
                <div className="t">{l.tipo === 'vale' ? 'Vale' : l.tipo === 'bonus' ? 'Bônus' : 'Desconto'} · {fmtDate(l.data)}</div>
                <div className="s">{l.motivo || (l.conta_pagar_id ? 'já pago pelo banco' : 'sem motivo anotado')}</div>
              </div>
              <div className={'v ' + (l.tipo === 'bonus' ? 'plus' : 'minus')}>
                {(l.tipo === 'bonus' ? '+ ' : '− ') + brl(l.valor)}
              </div>
            </div>
          ))}

          <div className="cl-resumo" style={{ marginTop: 18 }}>
            <div className="cl-sum">
              <span className="k">Horas pagas</span>
              <span className="calc">{fmtH(previa.horas).replace(' h', '')} × {previa.valor != null ? brl(previa.valor) : '—'}</span>
              <span className="v">R$ {brl(previa.bruto)}</span>
            </div>
            <div className="cl-sum"><span className="k">Bônus</span><span className="v" style={{ color: 'var(--tm-success)' }}>+ R$ {brl(previa.bonus)}</span></div>
            <div className="cl-sum"><span className="k">Descontos</span><span className="v">− R$ {brl(previa.descontos)}</span></div>
            <div className="cl-sum sub"><span className="k">Vales já pagos</span><span className="v" style={{ color: 'var(--tm-danger)' }}>− R$ {brl(previa.vales)}</span></div>
            <div className="cl-liq"><span className="k">Líquido a pagar</span><span className="v"><small>R$</small>{brl(previa.liquido)}</span></div>
          </div>

          {previa.precisaJornada && (
            <div className="cl-aviso warn" style={{ marginTop: 14 }}>
              <Icon name="alert-triangle" size={15} />
              <span>Há <b>folga ou feriado</b> no período e esta pessoa não tem <b>jornada por
                dia</b> no cadastro. Folga e feriado pagam a jornada — sem ela o dia concedido
                não tem valor, e o banco recusa o fechamento. Preencha em Pessoas › Cadastro.</span>
            </div>
          )}
          {previa.semValor && (
            <div className="cl-aviso warn" style={{ marginTop: 14 }}>
              <Icon name="alert-triangle" size={15} />
              <span>Sem valor definido — informe o pró-labore deste período acima.</span>
            </div>
          )}
          {previa.negativo && (
            <div className="cl-aviso warn" style={{ marginTop: 14 }}>
              <Icon name="alert-triangle" size={15} />
              <span>Os vales somam mais que o período rendeu. O sistema não gera título
                negativo nem zera em silêncio — ajuste um lançamento antes de fechar.</span>
            </div>
          )}

          <div className="cl-check" style={{ marginTop: 16 }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer' }}>
              <input type="checkbox" checked={jaPaguei} onChange={e => setJa(e.target.checked)} />
              <span>Já paguei agora</span>
            </label>
            {jaPaguei ? (
              <div style={{ display: 'flex', gap: 10, marginLeft: 'auto', alignItems: 'center' }}>
                <select className="input cl-in" value={conta} onChange={e => setConta(e.target.value)} style={{ minWidth: 160 }}>
                  <option value="">Conta bancária…</option>
                  {contas.map(c => <option key={c.id} value={c.id}>{c.nome}</option>)}
                </select>
                <select className="input cl-in" value={forma} onChange={e => setForma(e.target.value)}>
                  {['Transferência', 'PIX', 'Dinheiro'].map(f => <option key={f} value={f}>{f}</option>)}
                </select>
                <input className="input cl-in" type="date" value={dataPg} onChange={e => setDataPg(e.target.value)} />
              </div>
            ) : (
              <span className="cl-hint" style={{ marginLeft: 'auto' }}>
                O título nasce <b>Em aberto</b>, vencendo em {fmtDate(fim)}.
              </span>
            )}
          </div>

          <div style={{ marginTop: 14 }}>
            <Field label="Observação do título" value={obs} onChange={setObs} placeholder="" />
          </div>

          <div className="cl-nota" style={{ marginTop: 16 }}>
            Ao aplicar, de uma vez só: gera <b>1 título</b> em Contas a Pagar (categoria
            <b> Folha</b>), carimba <b>{dias.length} dia(s)</b> e <b>{lancs.length} lançamento(s)</b>
            como pagos, e trava o período — aplicar de novo <b>não gera segundo título</b>.
          </div>

          {erro ? <div className="cl-erro" style={{ marginTop: 14 }}>{erro}</div> : null}
        </>
      )}
    </Drawer>
  );
}

// ═══ TELA 3 · Fechamentos ═══════════════════════════════════════════════════
function ColaboradoresFechamentos({ onNav }) {
  const lista = useColaboradoresList([]);
  const [rows, setRows]     = React.useState([]);
  const [tits, setTits]     = React.useState({});
  const [filtro, setFiltro] = React.useState('');
  const [recarga, setRec]   = React.useState(0);
  const [erro, setErro]     = React.useState('');
  const [pagina, setPagina] = React.useState(1);
  const POR_PAGINA = 20;

  React.useEffect(() => {
    let vivo = true;
    tmFolha.fechamentos(filtro || null).then(async (fs) => {
      if (!vivo) return;
      setRows(fs);
      const ids = fs.map(f => f.conta_pagar_id).filter(Boolean);
      const m = await tmFolha.titulos(ids);
      if (vivo) setTits(m);
    });
    return () => { vivo = false; };
  }, [filtro, recarga]);

  const nomes = {};
  lista.rows.forEach(c => { nomes[c.id] = c.apelido || c.nome; });

  const totalPaginas = Math.max(1, Math.ceil(rows.length / POR_PAGINA));
  const atual = Math.min(Math.max(pagina, 1), totalPaginas);
  const pageRows = rows.slice((atual - 1) * POR_PAGINA, atual * POR_PAGINA);

  const podeDesfazer = tmPode('colaboradores', 'excluir');

  const desfazer = async (f) => {
    const t = tits[f.conta_pagar_id];
    if (t && t.status === 'Pago') return;
    if (!confirm('Desfazer o fechamento de ' + (nomes[f.colaborador_id] || '') + '?\n\n'
      + 'O título é apagado e os dias voltam a ficar em aberto.')) return;
    setErro('');
    const res = await tmFolha.desfazer(f.id);
    if (res && res.error) { setErro(colabMsgErro(res.error)); return; }
    setRec(r => r + 1);
  };

  return (
    <div className="canvas">
      <PageHead title="Pessoas · Fechamentos" sub="cada período fechado e o título que ele gerou"
        right={<>
          <SeletorPessoa rows={lista.rows} valor={filtro} onChange={(v) => { setFiltro(v); setPagina(1); }} />
          <Button variant="primary" icon="calendar-check" onClick={() => onNav && onNav('colaboradores/apontamento')}>
            Apontar e fechar
          </Button>
        </>} />

      {erro ? <div className="cl-erro">{erro}</div> : null}

      <Card padded={false}>
        {rows.length === 0 ? (
          <div style={{ padding: 28 }}>
            <EmptyState icon="calendar-check" title="Nenhum fechamento ainda"
              msg="Fechar um período gera o título no Contas a Pagar. Comece pelo apontamento da semana."
              action={<Button variant="primary" icon="arrow-right"
                onClick={() => onNav && onNav('colaboradores/apontamento')}>Ir para o apontamento</Button>} />
          </div>
        ) : (
          <>
            <table className="tbl cl-tbl">
              <thead><tr>
                <th>Pessoa</th><th>Período</th><th className="right">Horas</th>
                <th className="right">Bruto</th><th className="right">Vales</th>
                <th className="right">Líquido</th><th>Título</th><th></th>
              </tr></thead>
              <tbody>
                {pageRows.map(f => {
                  const t = tits[f.conta_pagar_id];
                  const cancelado = f.status === 'cancelado';
                  const pago = t && t.status === 'Pago';
                  return (
                    <tr key={f.id} style={cancelado ? { opacity: .5 } : null}>
                      <td><b>{nomes[f.colaborador_id] || '—'}</b></td>
                      <td>{fmtDate(f.periodo_inicio)} – {fmtDate(f.periodo_fim)}</td>
                      <td className="right">{fmtH(f.horas_total)}</td>
                      <td className="right">{brl(f.bruto)}</td>
                      <td className="right">{numBr(f.vales) > 0 ? '− ' + brl(f.vales) : '—'}</td>
                      <td className="right"><b>{brl(f.liquido)}</b></td>
                      <td>
                        {cancelado ? <Badge tone="neutral">desfeito</Badge>
                          : !f.conta_pagar_id ? <span className="muted">sem título (líquido zero)</span>
                          : pago ? <Badge tone="success">pago</Badge>
                          : <Badge tone="warning">em aberto</Badge>}
                      </td>
                      <td className="right">
                        {!cancelado && f.conta_pagar_id && (
                          <Button variant="ghost" size="sm" icon="rotate-ccw"
                            disabled={pago || !podeDesfazer}
                            onClick={() => desfazer(f)}
                            title={pago
                              ? 'O título já foi pago: o dinheiro saiu do banco. A correção entra como lançamento novo.'
                              : !podeDesfazer ? tmMotivoNegado('colaboradores', 'excluir')
                              : 'Desfazer este fechamento'}>
                            Desfazer
                          </Button>
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
            <FooterPager total={rows.length} perPage={POR_PAGINA} page={atual} onPage={setPagina} />
          </>
        )}
      </Card>

      <div className="cl-nota" style={{ marginTop: 14 }}>
        Fechamento aplicado é imutável — a trava está no banco, não só nesta tela.
        <b> Desfazer só existe enquanto o título não foi pago</b>: depois que o dinheiro saiu,
        desfazer seria mentira, e a correção entra como lançamento no próximo período.
      </div>
    </div>
  );
}

Object.assign(window, {
  ColaboradoresApontamento, ColaboradoresFechamentos, FechamentoDrawer,
  SeletorPessoa, FaixaPessoa,
});
