/* global React, ReactDOM, Icon, Button, Badge, Card, EmptyState, ModalCentral, PageHead, FooterPager,
          Field, SearchableSelect, fmtDate, tmPode, tmMotivoNegado, tmDb, tmCaixas, caixaMsgErro,
          useProdutosList, ESTOQUE_BLOQUEIOS, bloqueioLabel, empresaDoPedido, nomesParaResponsavel */

// ═══════════════════════════════════════════════════════════════════════════
//  ESTOQUE › CAIXAS DO DEPÓSITO
//
//  A etiqueta descreve uma CAIXA, não uma entrada: alguém pegou produto na
//  prateleira, contou, fechou e quer identificar. O produto pode ter chegado há
//  oito meses, sem nota — nada disso importa para quem está com a fita na mão.
//  Por isso lote e validade são OPCIONAIS: campo obrigatório aqui seria campo
//  inventado.
//
//  🔴 ISTO NÃO É INVENTÁRIO. Cada caixa guarda o que foi CONTADO na hora de
//     fechar; venda não passa por aqui (não sabe de qual caixa saiu). O
//     registro diverge da realidade com o tempo — é a natureza da coisa, e a
//     tela diz isso com todas as letras. "4 caixas" quer dizer "4 caixas foram
//     FECHADAS com este produto", nunca "existem 4 caixas cheias agora". Foi a
//     lição da contagem do BAL-0001 (CLAUDE.md § 7): número medido que envelhece
//     sozinho não pode vestir roupa de saldo vivo.
//
//  🔴 E A CAIXA NUNCA MEXE NO ESTOQUE. A tela LÊ o saldo; nenhuma função daqui
//     escreve em `produtos.estoque`, `estoque_bloqueado` ou movimentação. O
//     "bloqueado" da caixa só sai na etiqueta — o bloqueio de verdade continua
//     em Estoque › Bloqueio.
//
//  O FLUXO DA TARDE: escolhe o produto uma vez; cada Enter registra uma caixa
//  e mantém os campos. Registrar e imprimir são SEPARADOS porque cada
//  `window.print()` abre o diálogo do navegador, e isso não se pula — dez
//  caixas uma a uma seriam dez diálogos. A fila junta tudo numa impressão só.
//
//  O LEMBRETE DE REIMPRIMIR mora no banco (`corrigida_em` > `impressa_em`),
//  não no estado da tela: se ela corrigir, fechar a aba e voltar amanhã, a
//  caixa continua na fila, marcada como desatualizada.
// ═══════════════════════════════════════════════════════════════════════════

const CX_POR_PAGINA = 15;

// Dia LOCAL (aaaa-mm-dd). ⚠️ Chamada dentro de efeito/handler, NUNCA em array
// de dependência — relógio em deps já custou 143 milhões de requisições.
function cxHojeIso() {
  const d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
}

function cxUsuarioLogado() {
  const p = window.tmUserProfile || {};
  return p.nome || p.name || (window.tmUser && window.tmUser.email) || '';
}

// Data de timestamptz no fuso de quem olha (o `fmtDate` corta a string ISO,
// que está em UTC — impresso às 22h apareceria como amanhã).
function cxDataLocal(ts) {
  if (!ts) return '';
  const d = new Date(ts);
  return isNaN(d) ? '' : d.toLocaleDateString('pt-BR');
}

// ── Uma etiqueta (térmica 150 × 100 mm, a mesma página da etiqueta de volume)
// Campo vazio sai como LINHA EM BRANCO: no papel, linha vazia quer dizer "a
// preencher" — e produto antigo às vezes revela o lote depois.
function CxLinhaEtq({ k, v, mono }) {
  const vazio = v == null || String(v).trim() === '';
  return (
    <div className="etqc-lin">
      <span className="etqc-k">{k}</span>
      {vazio ? <span className="etqc-branco" /> : <span className={'etqc-v' + (mono ? ' mono' : '')}>{v}</span>}
    </div>
  );
}

function EtiquetaCaixa({ caixa, empresa }) {
  const nomeEmpresa = empresa.nome_fantasia || empresa.razao_social || empresa.nome || '';
  const [logoFalhou, setLogoFalhou] = React.useState(false);
  React.useEffect(() => { setLogoFalhou(false); }, [empresa.logo_url]);
  const nomeProduto = [caixa.produtoNome, caixa.variacaoNome].filter(Boolean).join(' · ');
  const sku = caixa.variacaoSku || caixa.produtoSku || '';
  return (
    <div className="etq-folha">
      <div className="etqc-borda">
        <div className="etqc-topo">
          <div className="etq-logo etqc-logo">
            {/* Logo da EMPRESA ATIVA — nada de fixar a TopMix (são 4 CNPJs). Se a
                URL falhar, o nome entra no lugar: caixa sem identificação é caixa
                anônima no galpão. */}
            {(empresa.logo_url && !logoFalhou)
              ? <img src={empresa.logo_url} alt={nomeEmpresa} onError={() => setLogoFalhou(true)} />
              : <div className="etq-logo-txt">{nomeEmpresa}</div>}
          </div>
          <div className="etqc-cx">
            <div className="etqc-cx-rot">CAIXA</div>
            <div className="etqc-cx-num">Nº {caixa.numero}</div>
            {/* "2 de 10" só quando as caixas saíram juntas: caixa registrada
                sozinha não tem total — ele não existia na hora de fechar. */}
            {caixa.leva_total ? <div className="etqc-cx-leva">{caixa.leva_n} DE {caixa.leva_total} DESTA LEVA</div> : null}
          </div>
        </div>
        <div className="etqc-prod">
          <div className="etqc-nome">{nomeProduto}</div>
          {sku ? <div className="etqc-sku">SKU {sku}</div> : null}
        </div>
        {caixa.bloqueio
          ? <div className="etqc-faixa">■ {String(bloqueioLabel(caixa.bloqueio)).toUpperCase()} — NÃO VENDER</div>
          : null}
        <div className="etqc-base">
          <div className="etqc-un">
            <div className="etqc-un-q">{caixa.unidades}</div>
            <div className="etqc-un-r">UNIDADES</div>
          </div>
          <div className="etqc-dados">
            <CxLinhaEtq k="LOTE" v={caixa.lote} mono />
            <CxLinhaEtq k="VALIDADE" v={caixa.validade ? fmtDate(caixa.validade) : ''} />
            <CxLinhaEtq k="FECHADA" v={fmtDate(caixa.fechada_em)} />
            <CxLinhaEtq k="RESPONSÁVEL" v={String(caixa.responsavel || '').toUpperCase()} />
          </div>
        </div>
      </div>
    </div>
  );
}

// ── Impressão da fila (ou de uma caixa só, na reimpressão) ──────────────────
// Só marca como impressa quando ELA confirma: o navegador não conta se a
// impressão foi cancelada, e marcar no clique apagaria o lembrete de uma
// etiqueta que nunca saiu.
function ImprimirCaixasOverlay({ caixas, empresa, onClose, onConfirmadas }) {
  const [perguntar, setPerguntar] = React.useState(false);
  const [gravando, setGravando] = React.useState(false);
  const [erro, setErro] = React.useState('');
  // A empresa (logo) vem da PÁGINA, buscada uma vez por visita — a prévia e a
  // impressão desenham a MESMA etiqueta, e a prévia redesenha a cada tecla.
  // (`empresaDoPedido({})` = empresa ativa do topo → primeira cadastrada, o
  // mesmo recurso da etiqueta de volume quando não há emissora.)

  const n = caixas.length;
  const imprimir = () => { window.print(); setPerguntar(true); };
  const confirmar = async () => {
    setGravando(true); setErro('');
    const r = await tmCaixas.marcarImpressas(caixas.map(c => c.id));
    setGravando(false);
    if (r && r.error) { setErro(caixaMsgErro(r.error)); return; }
    onConfirmadas && onConfirmadas(n);
  };

  return ReactDOM.createPortal(
    <div className="doc-print-overlay etq-root cxd-overlay"
      onClick={(e) => { if (e.target === e.currentTarget && !perguntar) onClose(); }}>
      <div className="doc-no-print cxd-printbar">
        {!perguntar ? (
          <div className="cxd-printbar-lin">
            <Icon name="printer" size={16} style={{ color: 'var(--tm-brand-champagne)' }} />
            <span className="cxd-printbar-tit">Etiquetas de caixa — {n}</span>
            <Button variant="primary" icon="printer" size="sm" onClick={imprimir} disabled={!empresa}>
              {`Imprimir ${n} etiqueta${n > 1 ? 's' : ''}`}
            </Button>
            <Button variant="secondary" size="sm" onClick={onClose}>Fechar</Button>
          </div>
        ) : (
          <div className="cxd-printbar-lin">
            <Icon name="help-circle" size={16} style={{ color: 'var(--tm-warning)' }} />
            <span className="cxd-printbar-tit">
              {n > 1 ? `As ${n} etiquetas saíram da impressora?` : 'A etiqueta saiu da impressora?'}
            </span>
            <Button variant="primary" icon="check" size="sm" onClick={confirmar} disabled={gravando}>
              {gravando ? 'Gravando…' : 'Sim, saíram'}
            </Button>
            <Button variant="secondary" size="sm" onClick={onClose} disabled={gravando}>Não — deixar na fila</Button>
          </div>
        )}
        {perguntar && (
          <div className="cxd-printbar-nota">
            Só marco como impressas quando você confirma: o navegador não avisa se a impressão foi
            cancelada. Respondendo "não", elas continuam na fila.
          </div>
        )}
        {erro ? <div className="cl-erro" style={{ marginTop: 8 }}>{erro}</div> : null}
      </div>
      <div className="etq-palco">
        {empresa
          ? caixas.map(c => <EtiquetaCaixa key={c.id} caixa={c} empresa={empresa} />)
          : <div className="doc-no-print" style={{ color: '#fff', padding: 40 }}>Carregando…</div>}
      </div>
    </div>,
    document.body
  );
}

// ── Corrigir uma caixa ──────────────────────────────────────────────────────
// O número, o produto e a leva NÃO mudam — estão no papel colado (o banco
// recusa com TM011). Todo o resto muda, e mudar deixa a etiqueta DESATUALIZADA:
// a guarda do banco marca `corrigida_em`, e a caixa volta para a fila.
function CaixaCorrigirModal({ caixa, onClose, onSalvo }) {
  const [unidades, setUnidades]       = React.useState(String(caixa.unidades));
  const [lote, setLote]               = React.useState(caixa.lote || '');
  const [validade, setValidade]       = React.useState(caixa.validade || '');
  const [bloqueio, setBloqueio]       = React.useState(caixa.bloqueio || '');
  const [responsavel, setResponsavel] = React.useState(caixa.responsavel || '');
  const [fechadaEm, setFechadaEm]     = React.useState(caixa.fechada_em || '');
  const [busy, setBusy] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const podeEditar = tmPode('estoque', 'editar');

  const salvar = async () => {
    const u = parseInt(unidades, 10);
    if (!(u > 0)) { setErro('Informe as unidades da caixa — um número maior que zero.'); return; }
    if (!fechadaEm) { setErro('Informe a data em que a caixa foi fechada.'); return; }
    setBusy(true); setErro('');
    const r = await tmCaixas.corrigir(caixa.id, { unidades: u, lote, validade, bloqueio, responsavel, fechadaEm });
    setBusy(false);
    if (r && r.error) { setErro(caixaMsgErro(r.error)); return; }
    onSalvo && onSalvo(r.caixa);
  };

  return (
    <ModalCentral onClose={onClose} width={560}
      eyebrow="Caixa do depósito"
      title={'Corrigir a caixa Nº ' + caixa.numero}
      subtitle={[caixa.produtoSku, caixa.produtoNome, caixa.variacaoNome].filter(Boolean).join(' · ')}
      footer={
        <>
          {!podeEditar && (
            <span className="cl-hint" style={{ marginRight: 'auto', maxWidth: '42ch' }}>
              {tmMotivoNegado('estoque', 'editar')}
            </span>
          )}
          <Button variant="ghost" onClick={onClose} disabled={busy}>Cancelar</Button>
          <Button variant="primary" icon="check" onClick={salvar} disabled={busy || !podeEditar}
            title={podeEditar ? '' : tmMotivoNegado('estoque', 'editar')}>
            {busy ? 'Salvando…' : 'Salvar correção'}
          </Button>
        </>
      }>
      <div className="cl-aviso info" style={{ marginBottom: 16 }}>
        <Icon name="lock" size={14} />
        <span>O <b>número</b> e o <b>produto</b> não mudam: já estão no papel colado. Produto errado?
          Exclua esta caixa e registre de novo no produto certo.</span>
      </div>
      <div className="field-grid-2">
        <Field label="Unidades na caixa">
          <input className="input" type="number" min="1" step="1" value={unidades}
            onChange={e => setUnidades(e.target.value)} style={{ textAlign: 'right' }} />
        </Field>
        <Field label="Fechada em">
          <input className="input" type="date" value={String(fechadaEm).slice(0, 10)}
            onChange={e => setFechadaEm(e.target.value)} />
        </Field>
      </div>
      <div className="field-grid-2" style={{ marginTop: 14 }}>
        <Field label="Lote"><input className="input" value={lote} onChange={e => setLote(e.target.value)} /></Field>
        <Field label="Validade">
          <input className="input" type="date" value={String(validade).slice(0, 10)}
            onChange={e => setValidade(e.target.value)} />
        </Field>
      </div>
      <div className="field-grid-2" style={{ marginTop: 14 }}>
        <Field label="Conteúdo">
          <select className="input" value={bloqueio} onChange={e => setBloqueio(e.target.value)}>
            <option value="">Pronto para venda</option>
            {(ESTOQUE_BLOQUEIOS || []).map(b => <option key={b.id} value={b.id}>Bloqueado · {b.label}</option>)}
          </select>
        </Field>
        <Field label="Responsável">
          <input className="input" value={responsavel} onChange={e => setResponsavel(e.target.value)} />
        </Field>
      </div>
      <div className="cl-aviso warn" style={{ marginTop: 16 }}>
        <Icon name="printer" size={14} />
        <span>Salvar deixa a etiqueta colada <b>desatualizada</b>. A caixa volta para a fila de
          impressão — reimprima e <b>troque</b> a etiqueta: número certo com quantidade errada é pior
          que etiqueta nenhuma, porque parece confiável.</span>
      </div>
      {erro ? <div className="cl-erro" style={{ marginTop: 14 }}>{erro}</div> : null}
    </ModalCentral>
  );
}

// Situação da etiqueta, em palavras. Esvaziada vem primeiro: a etiqueta de uma
// caixa que acabou não pede nada.
function CxSituacao({ c }) {
  if (c.esvaziada)     return <span className="muted">esvaziada {cxDataLocal(c.esvaziada_em)}</span>;
  if (c.desatualizada) return <Badge tone="warning">desatualizada</Badge>;
  if (c.naoImpressa)   return <Badge tone="neutral">não impressa</Badge>;
  return <span className="muted">impressa {cxDataLocal(c.impressa_em)}</span>;
}

// ── A tela ──────────────────────────────────────────────────────────────────
function CaixasDeposito({ onNav }) {
  const prods = useProdutosList([]);
  const [produtoId, setProdutoId]     = React.useState('');
  const [variacoes, setVariacoes]     = React.useState([]);
  const [variacaoId, setVariacaoId]   = React.useState('');
  // Uma quantidade POR CAIXA, na ordem da tela — as caixas do galpão não são
  // padronizadas (19, 142, 50, e uma incompleta). `quantas` é quantas ela vai
  // registrar agora; `linhas` pode ser MAIOR que isso: diminuir só deixa de
  // mostrar, para que voltar a aumentar não perca o que ela já digitou.
  const [linhas, setLinhas]           = React.useState(['']);
  const [quantas, setQuantas]         = React.useState(1);
  const [foco, setFoco]               = React.useState(0);      // linha em foco = a da prévia
  const [empresa, setEmpresa]         = React.useState(null);   // logo: UMA busca por visita
  const [ampliada, setAmpliada]       = React.useState(false);
  const [lote, setLote]               = React.useState('');
  const [validade, setValidade]       = React.useState('');
  const [bloqueio, setBloqueio]       = React.useState('');
  const [responsavel, setResponsavel] = React.useState(() => cxUsuarioLogado());
  const [fechadaEm, setFechadaEm]     = React.useState(() => cxHojeIso());
  const [sugestoes, setSugestoes]     = React.useState([]);
  const [caixas, setCaixas]           = React.useState(null);   // do produto; null = carregando / nenhum
  const [fila, setFila]               = React.useState([]);
  const [hoje, setHoje]               = React.useState(null);
  const [reloadKey, setReloadKey]     = React.useState(0);       // chave estável — nunca relógio
  const [busy, setBusy]               = React.useState(false);
  const [erro, setErro]               = React.useState('');
  const [aviso, setAviso]             = React.useState(null);    // { tom: 'ok'|'warn', texto }
  const [corrigindo, setCorrigindo]   = React.useState(null);
  const [imprimindo, setImprimindo]   = React.useState(null);
  const [pagina, setPagina]           = React.useState(1);
  const unidadesRef = React.useRef(null);   // 1ª caixa — o foco volta para cá
  const linhasRef   = React.useRef(null);   // a lista, para pular de linha no Enter
  const prefillDe   = React.useRef(null);   // produto cujo "repete a última caixa" já rodou

  const podeCriar   = tmPode('estoque', 'criar');
  const podeEditar  = tmPode('estoque', 'editar');
  const podeExcluir = tmPode('estoque', 'excluir');

  const produtos = (prods.rows || []).filter(p => !p.ehKit);
  const produto  = produtos.find(p => p.id === produtoId) || null;
  const variacao = variacoes.find(v => v.id === variacaoId) || null;
  const opts = produtos.map(p => ({ value: p.id, label: (p.sku ? p.sku + ' · ' : '') + (p.name || '') }));

  React.useEffect(() => {
    let vivo = true;
    nomesParaResponsavel().then(n => { if (vivo) setSugestoes(n || []); });
    // A empresa (logo) serve a PRÉVIA e a impressão. Buscada UMA vez por visita
    // e guardada em estado: a prévia redesenha a cada tecla, e nada que ela
    // desenhe pode custar rede (CLAUDE.md § 7, os 304 GB).
    empresaDoPedido({}).then(e => { if (vivo) setEmpresa(e || {}); });
    return () => { vivo = false; };
  }, []);

  // Fila (todas as caixas de todos os produtos que pedem etiqueta) e o "Hoje".
  React.useEffect(() => {
    let vivo = true;
    (async () => {
      const [f, hj] = await Promise.all([tmCaixas.fila(), tmCaixas.contarHoje(cxHojeIso())]);
      if (!vivo) return;
      setFila((f && f.caixas) || []);
      setHoje(hj && !hj.error ? hj : null);
    })();
    return () => { vivo = false; };
  }, [reloadKey]);

  // Caixas e variações do produto escolhido.
  React.useEffect(() => {
    if (!produtoId) { setCaixas(null); setVariacoes([]); return undefined; }
    let vivo = true;
    (async () => {
      const temVar = !!(produto && produto.temVariacoes);
      const [cx, vs] = await Promise.all([
        tmCaixas.doProduto(produtoId),
        temVar ? tmDb.loadVariacoes(produtoId) : Promise.resolve([]),
      ]);
      if (!vivo) return;
      if (cx && cx.error) setErro(caixaMsgErro(cx.error));
      const lista = (cx && cx.caixas) || [];
      setCaixas(lista);
      setVariacoes((vs || []).filter(v => v.ativo !== false));
      // "Repete o da última caixa", uma vez por produto, e só no que está vazio:
      // o que ela já digitou manda.
      if (prefillDe.current !== produtoId) {
        prefillDe.current = produtoId;
        const ultima = lista[0];   // vem ordenada por número, da maior para a menor
        if (ultima) {
          setLote(v => v || ultima.lote || '');
          setValidade(v => v || ultima.validade || '');
          setLinhas(a => (String(a[0] || '').trim() ? a : [String(ultima.unidades || ''), ...a.slice(1)]));
        }
      }
    })();
    return () => { vivo = false; };
  // eslint-disable-next-line
  }, [produtoId, reloadKey]);

  const escolherProduto = (id) => {
    const p = produtos.find(x => x.id === id) || null;
    setProdutoId(id || '');
    setVariacaoId(''); setLote(''); setValidade(''); setBloqueio(''); setQuantas(1); setPagina(1); setFoco(0);
    setLinhas([p && p.unidadesCaixaDeposito ? String(p.unidadesCaixaDeposito) : '']);
    setErro(''); setAviso(null);
    prefillDe.current = null;
  };

  const recarregar = () => setReloadKey(k => k + 1);

  // ── as linhas de quantidade ───────────────────────────────────────────────
  // A CAIXA 1 manda: linha nova nasce com o valor dela. É por isso que não
  // existe mais um "modo N caixas iguais" — três iguais é digitar uma vez e
  // dizer 3; três diferentes é mexer nas outras duas. Um modo a menos para
  // manter e para explicar.
  const mudarQuantas = (v) => {
    setQuantas(v);
    const q = Math.max(1, Math.min(200, parseInt(v, 10) || 1));
    setLinhas(a => {
      if (a.length >= q) return a;                 // diminuir NÃO apaga: só esconde
      const b = a.slice();
      const padrao = String(a[0] || '');
      while (b.length < q) b.push(padrao);
      return b;
    });
  };
  const mudarLinha = (i, v) => setLinhas(a => {
    const b = a.slice();
    while (b.length <= i) b.push('');
    b[i] = v;
    return b;
  });
  const igualarTodas = () => setLinhas(a => {
    const b = a.slice();
    const q = Math.max(1, Math.min(200, parseInt(quantas, 10) || 1));
    for (let i = 1; i < q; i++) b[i] = String(a[0] || '');
    return b;
  });
  const focarLinha = (i) => {
    const campos = linhasRef.current ? linhasRef.current.querySelectorAll('input') : null;
    const el = (campos && campos[i]) || unidadesRef.current;
    if (el) { el.focus(); if (el.select) el.select(); }
  };
  // Enter dentro da lista pula para a próxima caixa; na ÚLTIMA, o form
  // registra. Sem isso o Enter viraria armadilha: registrar com metade das
  // linhas ainda no padrão.
  const pularLinha = (e, i) => {
    const q = Math.max(1, Math.min(200, parseInt(quantas, 10) || 1));
    if (e.key !== 'Enter' || i >= q - 1) return;
    e.preventDefault();
    focarLinha(i + 1);
  };

  const registrar = async (e) => {
    if (e) e.preventDefault();
    setErro('');
    if (!produto) { setErro('Escolha o produto.'); return; }
    const q = qNum;
    if (q < 1 || q > 200) { setErro('Quantas caixas: de 1 a 200.'); return; }
    const lista = Array.from({ length: q }, (_, i) => parseInt(linhas[i], 10));
    // Caixa sem quantidade RECUSA, nomeando a linha. Ignorá-la faria
    // "Registrar as 3" gravar 2 — o botão mentindo sobre o que fez.
    const ruim = lista.findIndex(n => !(n > 0));
    if (ruim >= 0) {
      setErro(q > 1
        ? `A caixa ${ruim + 1} desta leva está sem quantidade. Preencha ou diminua "quantas caixas" — nenhuma foi registrada.`
        : 'Informe quantas unidades foram nesta caixa — um número maior que zero.');
      focarLinha(ruim);
      return;
    }
    setBusy(true);
    const r = await tmCaixas.registrar({
      produtoId, variacaoId: variacaoId || null, unidades: lista,
      lote, validade: validade || null, responsavel, bloqueio: bloqueio || null, fechadaEm,
    });
    setBusy(false);
    if (r.error) { setErro(caixaMsgErro(r.error)); return; }
    const nums = (r.caixas || []).map(c => c.numero);
    setAviso({ tom: 'ok', texto: nums.length === 1
      ? `Caixa ${nums[0]} registrada — está na fila de impressão.`
      : `Caixas ${nums[0]} a ${nums[nums.length - 1]} registradas (${nums.length}) — estão na fila de impressão.` });
    setQuantas(1); setFoco(0);
    recarregar();
    // O foco volta para "Unidades", com o número selecionado: a próxima caixa
    // é só digitar (ou nem isso) e Enter de novo.
    setTimeout(() => { if (unidadesRef.current) { unidadesRef.current.focus(); unidadesRef.current.select(); } }, 0);
  };

  const esvaziar = async (c, sim) => {
    setErro('');
    const r = await tmCaixas.esvaziar(c.id, sim);
    if (r && r.error) { setErro(caixaMsgErro(r.error)); return; }
    recarregar();
  };

  const excluir = async (c) => {
    // O banco numera por max(numero)+1: excluir a ÚLTIMA devolve o número para
    // a próxima caixa. O texto tem que dizer isso — "o número deixa de existir"
    // seria mentira justamente no caso em que a etiqueta velha vira perigosa.
    const ultima = c.numero === Math.max.apply(null, (caixas || []).map(x => Number(x.numero) || 0));
    const txt = `Excluir a caixa Nº ${c.numero} de ${c.produtoSku || 'este produto'}?\n\n`
      + (ultima
          ? `Como é a última, o Nº ${c.numero} volta a ser usado pela próxima caixa deste produto.\n`
          : `O Nº ${c.numero} fica vago — os números das outras caixas não mudam.\n`)
      + (c.impressa_em
          ? (ultima
              ? 'A etiqueta dela já foi impressa: descole-a, senão vão existir duas caixas com o mesmo número.\n'
              : 'A etiqueta dela já foi impressa: se estiver colada, descole-a.\n')
          : '')
      + 'O estoque não muda: a caixa nunca mexeu nele.';
    if (!confirm(txt)) return;
    setErro('');
    const r = await tmCaixas.excluir(c.id);
    if (r && r.error) { setErro(caixaMsgErro(r.error)); return; }
    recarregar();
  };

  // ── números do resumo — ver o comentário do topo: registro, não inventário
  const doResumo = (caixas || []).filter(c => !c.esvaziada && (!variacaoId || c.variacao_id === variacaoId));
  const somaUn   = doResumo.reduce((a, c) => a + (Number(c.unidades) || 0), 0);
  const estoqueHoje = variacao ? (Number(variacao.est) || 0) : (produto ? (Number(produto.est) || 0) : 0);
  const diferenca = estoqueHoje - somaUn;
  const nEsvaziadas = (caixas || []).filter(c => c.esvaziada).length;
  const proximo = caixas && caixas.length ? Math.max.apply(null, caixas.map(c => Number(c.numero) || 0)) + 1 : 1;
  const qNum = Math.max(1, Math.min(200, parseInt(quantas, 10) || 1));
  const totalUn = Array.from({ length: qNum }, (_, i) => parseInt(linhas[i], 10) || 0).reduce((a, b) => a + b, 0);
  // A prévia é a caixa em que ela está pensando AGORA: a linha em foco. Objeto
  // derivado do formulário — não é rascunho, não grava nada.
  const iPrev = Math.min(Math.max(foco, 0), qNum - 1);
  const caixaPrevia = {
    numero: proximo + iPrev,
    leva_n:     qNum > 1 ? iPrev + 1 : null,
    leva_total: qNum > 1 ? qNum : null,
    produtoSku:   produto ? (produto.sku || '') : '',
    produtoNome:  produto ? (produto.name || '') : '',
    variacaoNome: variacao ? (variacao.nome || '') : '',
    variacaoSku:  variacao ? (variacao.sku || '') : '',
    unidades: linhas[iPrev] || '',
    lote, validade: validade || '', fechada_em: fechadaEm, responsavel, bloqueio: bloqueio || '',
  };
  const desatualizadas = fila.filter(c => c.desatualizada);

  const totalPaginas = Math.max(1, Math.ceil((caixas || []).length / CX_POR_PAGINA));
  const atual = Math.min(Math.max(pagina, 1), totalPaginas);   // filtrar não pode encalhar
  const pageRows = (caixas || []).slice((atual - 1) * CX_POR_PAGINA, atual * CX_POR_PAGINA);

  return (
    <div className="canvas">
      <PageHead title="Caixas do depósito"
        sub="Escolha o produto uma vez; cada Enter registra uma caixa. A etiqueta sai da fila de impressão."
        right={hoje ? <Badge tone="brand">Hoje: {hoje.caixas} caixa{hoje.caixas === 1 ? '' : 's'} · {hoje.produtos} produto{hoje.produtos === 1 ? '' : 's'}</Badge> : null} />

      {aviso ? (
        <div className={'cl-aviso ' + (aviso.tom === 'warn' ? 'warn' : 'ok')} style={{ marginBottom: 14 }}>
          <Icon name={aviso.tom === 'warn' ? 'alert-triangle' : 'check-circle'} size={14} />
          <span>{aviso.texto}</span>
        </div>
      ) : null}

      <div className="cxd-cols">
        {/* ── o formulário: fica, e os campos também ── */}
        <form className="card cxd-form" onSubmit={registrar}>
          <div className="card-head"><h3>Nova caixa</h3></div>
          <div className="cxd-form-body">
            <SearchableSelect label="Produto" value={produtoId} onChange={escolherProduto} options={opts}
              placeholder="SKU ou nome do produto…" />
            {produto && produto.unidadesCaixaDeposito ? (
              <div className="cxd-dica">Padrão do produto: <b>{produto.unidadesCaixaDeposito} un. por caixa</b></div>
            ) : null}

            {produto && produto.temVariacoes ? (
              <Field label="Variação">
                <select className="input" value={variacaoId} onChange={e => setVariacaoId(e.target.value)}>
                  <option value="">Sem variação — a caixa é do produto</option>
                  {variacoes.map(v => <option key={v.id} value={v.id}>{v.nome}{v.sku ? ' · ' + v.sku : ''}</option>)}
                </select>
              </Field>
            ) : null}

            <div className="field-grid-2">
              {qNum === 1 ? (
                <Field label="Unidades nesta caixa">
                  <input ref={unidadesRef} className="input cxd-grande" type="number" min="1" step="1"
                    value={linhas[0] == null ? '' : linhas[0]}
                    onChange={e => mudarLinha(0, e.target.value)} onFocus={() => setFoco(0)} />
                </Field>
              ) : (
                /* O total ocupa o lugar exato onde a quantidade única estava:
                   é o número que ela compara com o monte na frente dela. */
                <Field label="Total da leva">
                  <div className="input cxd-grande cxd-total-campo" aria-live="polite">{totalUn} un</div>
                </Field>
              )}
              <Field label="Quantas caixas">
                <div className="cxd-stepper">
                  <button type="button" onClick={() => mudarQuantas(qNum - 1)}
                    aria-label="Uma caixa a menos">−</button>
                  <input className="input" type="number" min="1" max="200" step="1" value={quantas}
                    onChange={e => mudarQuantas(e.target.value)} />
                  <button type="button" onClick={() => mudarQuantas(qNum + 1)}
                    aria-label="Uma caixa a mais">+</button>
                </div>
              </Field>
            </div>

            {qNum > 1 ? (
              <div className="cxd-linhas" ref={linhasRef} role="group" aria-label="Quantidade de cada caixa">
                {Array.from({ length: qNum }).map((_, i) => (
                  <label className={'cxd-linha' + (i === iPrev ? ' foco' : '')} key={i}>
                    <span className="cxd-linha-rot">caixa {i + 1}</span>
                    <input ref={i === 0 ? unidadesRef : null} className="input" type="number" min="1" step="1"
                      value={linhas[i] == null ? '' : linhas[i]}
                      onChange={e => mudarLinha(i, e.target.value)}
                      onFocus={() => setFoco(i)}
                      onKeyDown={e => pularLinha(e, i)} />
                  </label>
                ))}
                <div className="cxd-linhas-pe">
                  <span>Enter passa para a caixa seguinte</span>
                  <button type="button" className="cxd-link" onClick={igualarTodas}>igualar todas à caixa 1</button>
                </div>
              </div>
            ) : null}

            <div className="field-grid-2">
              <Field label="Lote (opcional)">
                <input className="input" value={lote} onChange={e => setLote(e.target.value)} />
              </Field>
              <Field label="Validade (opcional)">
                <input className="input" type="date" value={String(validade).slice(0, 10)}
                  onChange={e => setValidade(e.target.value)} />
              </Field>
            </div>
            {/* O consumidor dos switches "Controla lote / validade" do cadastro:
                AVISA, nunca trava — estoque antigo nem sempre sabe. */}
            {produto && produto.controlaLote && !String(lote).trim() ? (
              <div className="cxd-alerta">Este produto controla lote — preencha se souber. Não impede de registrar.</div>
            ) : null}
            {produto && produto.controlaValidade && !validade ? (
              <div className="cxd-alerta">Este produto controla validade — preencha se souber. Não impede de registrar.</div>
            ) : null}

            <Field label="Conteúdo">
              <select className="input" value={bloqueio} onChange={e => setBloqueio(e.target.value)}>
                <option value="">Pronto para venda</option>
                {(ESTOQUE_BLOQUEIOS || []).map(b => <option key={b.id} value={b.id}>Bloqueado · {b.label}</option>)}
              </select>
            </Field>
            {bloqueio ? (
              <div className="cxd-dica">Sai em faixa preta na etiqueta: <b>{String(bloqueioLabel(bloqueio)).toUpperCase()} — NÃO VENDER</b>.
                Não mexe no estoque bloqueado; isso é em Estoque › Bloqueio.</div>
            ) : null}

            <div className="field-grid-2">
              <Field label="Responsável">
                <input className="input" value={responsavel} list="cxd-responsaveis"
                  onChange={e => setResponsavel(e.target.value)} placeholder="Quem fechou a caixa" />
              </Field>
              <Field label="Fechada em">
                <input className="input" type="date" value={fechadaEm} onChange={e => setFechadaEm(e.target.value)} />
              </Field>
            </div>
            {sugestoes.length > 0 && (
              <datalist id="cxd-responsaveis">
                {sugestoes.map(nome => <option key={nome} value={nome} />)}
              </datalist>
            )}

            {erro ? <div className="cl-erro">{erro}</div> : null}
            {!podeCriar ? <div className="cl-hint">{tmMotivoNegado('estoque', 'criar')}</div> : null}

            <Button variant="primary" icon="package-plus" disabled={busy || !produto || !podeCriar}
              title={podeCriar ? 'Enter registra' : tmMotivoNegado('estoque', 'criar')}>
              {busy ? 'Registrando…'
                : qNum > 1 ? `Registrar caixas ${proximo} a ${proximo + qNum - 1}`
                : `Registrar caixa ${proximo}`}
              <span className="cxd-kbd">Enter</span>
            </Button>
          </div>
        </form>

        {/* ── a coluna da direita ── */}
        <div className="cxd-dir">
          {/* Prévia e fila dividem a primeira faixa: as duas falam do papel que
              está para sair. O formulário não desce um pixel — é outra coluna. */}
          <div className="cxd-topo-dir">
          {produto ? (
            <Card title="Prévia da etiqueta" style={{ flex: '0 0 340px' }}
              action={<Button variant="secondary" size="sm" icon="maximize-2"
                onClick={() => setAmpliada(true)}>Tamanho real</Button>}>
              <div className="cxd-prev-corpo">
                {/* É o MESMO componente que imprime, só em escala. Uma segunda
                    marcação "versão da tela" divergiria na primeira mudança, e a
                    prévia passaria a mentir justo quando ela mais confia. */}
                <button type="button" className="cxd-prev" onClick={() => setAmpliada(true)}
                  title="Clique para ver em tamanho real">
                  <div className="etq-root cxd-prev-palco">
                    <div className="cxd-prev-escala">
                      <EtiquetaCaixa caixa={caixaPrevia} empresa={empresa || {}} />
                    </div>
                  </div>
                </button>
                <div className="cxd-dica">
                  {qNum > 1 ? <>Mostrando a <b>caixa {iPrev + 1} de {qNum}</b> — clique numa linha para ver a dela. </> : null}
                  Nesta escala dá para ver o que <b>não coube</b> e a faixa preta; para <b>ler</b> o lote e o nome, clique na etiqueta.
                </div>
              </div>
            </Card>
          ) : null}
          <Card title={'Fila de impressão' + (fila.length ? ` · ${fila.length}` : '')}
            action={fila.length ? (
              <Button variant="primary" icon="printer" size="sm" onClick={() => setImprimindo(fila)}>
                {`Imprimir ${fila.length} etiqueta${fila.length > 1 ? 's' : ''}`}
              </Button>
            ) : null}>
            <div className="cxd-fila-body">
              {desatualizadas.length > 0 ? (
                <div className="cl-aviso warn">
                  <Icon name="alert-triangle" size={14} />
                  <span><b>{desatualizadas.length} etiqueta{desatualizadas.length > 1 ? 's' : ''} desatualizada{desatualizadas.length > 1 ? 's' : ''}</b>:
                    a caixa foi corrigida depois de impressa. Reimprima e <b>troque</b> a etiqueta colada.</span>
                </div>
              ) : null}
              {fila.length === 0 ? (
                <div className="cxd-vazio">Nada na fila — toda caixa registrada tem etiqueta confirmada.</div>
              ) : (
                <div className="cxd-fila">
                  {fila.map(c => (
                    <span key={c.id} className={'cxd-chip' + (c.desatualizada ? ' desat' : '')}
                      title={[c.produtoSku, c.produtoNome, c.variacaoNome].filter(Boolean).join(' · ')}>
                      <b>CX {c.numero}</b> {c.produtoSku || ''} · {c.unidades} un.
                      {c.desatualizada ? ' · desatualizada' : ''}
                    </span>
                  ))}
                </div>
              )}
            </div>
          </Card>
          </div>

          {!produto ? (
            <Card>
              <EmptyState icon="package" title="Escolha um produto"
                msg="As caixas registradas dele aparecem aqui, com o estoque de hoje ao lado." />
            </Card>
          ) : (
            <>
              <Card title={'Registro de caixas · ' + (produto.sku || produto.name)}>
                <div className="cxd-kpis">
                  <div className="cxd-kp">
                    <span className="k">{variacao ? 'Estoque da variação hoje' : 'Estoque do produto hoje'}</span>
                    <span className="v">{estoqueHoje}<small>un.</small></span>
                  </div>
                  <div className="cxd-kp">
                    <span className="k">Fechado em caixas</span>
                    <span className="v">{somaUn}<small>un.</small></span>
                    <span className="s">{doResumo.length} caixa{doResumo.length === 1 ? '' : 's'} fechada{doResumo.length === 1 ? '' : 's'}
                      {nEsvaziadas ? ` · ${nEsvaziadas} marcada${nEsvaziadas > 1 ? 's' : ''} como esvaziada${nEsvaziadas > 1 ? 's' : ''}` : ''}</span>
                  </div>
                  <div className="cxd-kp">
                    <span className="k">Estoque − caixas</span>
                    <span className={'v' + (diferenca < 0 ? ' neg' : '')}>{diferenca}<small>un.</small></span>
                  </div>
                </div>
                {/* 🔴 A frase que impede de confiar num número que envelhece
                    sozinho. Visível sempre — não é tooltip, é o que o número é. */}
                <div className="cxd-honesto">
                  <b>Isto não é inventário.</b> Cada caixa guarda o que foi <b>contado na hora de fechar</b>.
                  Venda não passa por aqui — não sabe de qual caixa saiu —, então estes números envelhecem
                  sozinhos. "{doResumo.length} caixa{doResumo.length === 1 ? '' : 's'}" quer dizer que {doResumo.length === 1 ? 'ela foi fechada' : 'elas foram fechadas'} com
                  este produto, não que {doResumo.length === 1 ? 'esteja cheia' : 'estejam cheias'} agora. Quando uma caixa acabar, marque <b>Esvaziar</b>.
                </div>
                {diferenca < 0 ? (
                  <div className="cxd-alerta" style={{ margin: '0 16px 14px' }}>
                    As caixas fechadas somam mais que o estoque de hoje — normal depois de vendas. Alguma já
                    foi aberta? Marque Esvaziar nela. Nada disso impede de registrar.
                  </div>
                ) : null}
              </Card>

              <Card title={'Caixas de ' + (produto.sku || produto.name)}>
                {caixas === null ? (
                  <div className="cxd-vazio">Carregando…</div>
                ) : caixas.length === 0 ? (
                  <div className="cxd-vazio">Nenhuma caixa registrada ainda para este produto.</div>
                ) : (
                  <>
                    <table className="tbl tbl-fixa" style={{ fontSize: 12.5 }}>
                      <colgroup>
                        <col style={{ width: 70 }} /><col style={{ width: 92 }} /><col />
                        <col style={{ width: 96 }} /><col style={{ width: 132 }} /><col style={{ width: 120 }} />
                        <col style={{ width: 132 }} /><col style={{ width: 142 }} />
                      </colgroup>
                      <thead><tr>
                        <th>Caixa</th><th className="num right">Un. ao fechar</th><th>Lote</th>
                        <th>Validade</th><th>Fechada</th><th>Conteúdo</th><th>Etiqueta</th><th></th>
                      </tr></thead>
                      <tbody>
                        {pageRows.map(c => (
                          <tr key={c.id} className={c.esvaziada ? 'cxd-esvaziada' : ''}>
                            <td className="cxd-num">CX {c.numero}{c.leva_total ? <span className="cxd-leva"> {c.leva_n}/{c.leva_total}</span> : null}</td>
                            <td className="num right">{c.unidades}</td>
                            <td className="trunca" title={c.lote || ''}>{c.lote || <span className="muted">não informado</span>}</td>
                            <td>{c.validade ? fmtDate(c.validade) : <span className="muted">não informada</span>}</td>
                            <td className="trunca" title={c.responsavel || ''}>{fmtDate(c.fechada_em)}{c.responsavel ? ' · ' + c.responsavel : ''}</td>
                            <td>{c.bloqueio ? <Badge tone="danger">{bloqueioLabel(c.bloqueio)}</Badge> : <span className="muted">pronto p/ venda</span>}</td>
                            <td><CxSituacao c={c} /></td>
                            <td>
                              <div className="cxd-acoes">
                                {!c.esvaziada && (
                                  <button type="button" className="cxd-ico" title={podeEditar ? 'Corrigir a caixa' : tmMotivoNegado('estoque', 'editar')}
                                    disabled={!podeEditar} onClick={() => setCorrigindo(c)}><Icon name="pencil" size={14} /></button>
                                )}
                                {!c.esvaziada && (
                                  <button type="button" className="cxd-ico" title="Reimprimir a etiqueta"
                                    onClick={() => setImprimindo([c])}><Icon name="printer" size={14} /></button>
                                )}
                                <button type="button" className="cxd-ico"
                                  title={!podeCriar ? tmMotivoNegado('estoque', 'criar') : c.esvaziada ? 'Desfazer: a caixa não acabou' : 'Esvaziar: a caixa acabou'}
                                  disabled={!podeCriar} onClick={() => esvaziar(c, !c.esvaziada)}>
                                  <Icon name={c.esvaziada ? 'undo-2' : 'package-open'} size={14} />
                                </button>
                                <button type="button" className="cxd-ico perigo"
                                  title={podeExcluir ? 'Excluir a caixa' : tmMotivoNegado('estoque', 'excluir')}
                                  disabled={!podeExcluir} onClick={() => excluir(c)}><Icon name="trash-2" size={14} /></button>
                              </div>
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                    {caixas.length > CX_POR_PAGINA ? (
                      <FooterPager total={caixas.length} perPage={CX_POR_PAGINA} page={atual} onPage={setPagina} />
                    ) : null}
                  </>
                )}
              </Card>
            </>
          )}
        </div>
      </div>

      {corrigindo ? (
        <CaixaCorrigirModal caixa={corrigindo} onClose={() => setCorrigindo(null)}
          onSalvo={(c) => {
            setCorrigindo(null);
            setAviso({ tom: 'warn', texto: `Caixa ${(c && c.numero) || corrigindo.numero} corrigida. A etiqueta colada nela está desatualizada — ela voltou para a fila: reimprima e troque.` });
            recarregar();
          }} />
      ) : null}
      {imprimindo ? (
        <ImprimirCaixasOverlay caixas={imprimindo} empresa={empresa} onClose={() => setImprimindo(null)}
          onConfirmadas={(n) => {
            setImprimindo(null);
            setAviso({ tom: 'ok', texto: n > 1 ? `${n} etiquetas confirmadas como impressas.` : 'Etiqueta confirmada como impressa.' });
            recarregar();
          }} />
      ) : null}
      {/* Tamanho real: a MESMA etiqueta, sem escala. Em 300 px o valor do lote
          sai a 7,8 px — dá para ver que transbordou, não para ler o que está
          escrito, e metade do valor da prévia é conferir o lote e o nome antes
          de gastar papel. */}
      {ampliada && produto ? (
        <ModalCentral title={`Etiqueta da caixa ${caixaPrevia.numero}`}
          subtitle="Tamanho real, 150 × 100 mm — a mesma que vai para a impressora."
          width={640} onClose={() => setAmpliada(false)}>
          <div className="etq-root cxd-prev-real">
            <EtiquetaCaixa caixa={caixaPrevia} empresa={empresa || {}} />
          </div>
        </ModalCentral>
      ) : null}
    </div>
  );
}

Object.assign(window, { CaixasDeposito, EtiquetaCaixa, ImprimirCaixasOverlay, CaixaCorrigirModal });
