/* global React, ReactDOM, Icon, Button, Badge, Card, Field, PageHead, SearchableSelect, fmtDate, bloqueioLabel,
          tmDb, tmCaixas, caixaMsgErro, eanLimpo, eanValido, cbItensDoCatalogo, cbIndice, cbSituacao,
          cbConferencia, cbResolverLeitura, cbFormatoDe, CB_TESTE_EAN, CB_TESTE_CAIXA */

// ═══════════════════════════════════════════════════════════════════════════
//  CÓDIGO DE BARRAS — CB1 (21/10/2026)
//
//  O EAN do RÓTULO quando ele existe, é válido e é só daquele item; para os outros, o CÓDIGO INTERNO do app (EAN-13 de
//  uso interno da GS1, "200…", gerado no banco). A regra de qual vale mora no db.js (`cbSituacao`), igual à do banco.
//  Aqui moram as duas telas e as etiquetas:
//   · Produtos › Códigos de barras (`produtos/codigos`): os EANs repetidos (o dono se escolhe LENDO O FRASCO), os com o
//     dígito errado, os sem código (o "Gerar código"), e um campo para ler e IDENTIFICAR — nada grava por ele;
//   · Estoque › Etiquetas de código (`estoque/etiquetas`): a fila das etiquetas 80 × 50 mm de produto e de caixa, a
//     etiqueta de teste e o mesmo campo de leitura.
//  🔴 NADA AQUI MEXE EM ESTOQUE. Ler um código só identifica. Entrada/saída pelo leitor é o CB2.
//
//  AS BARRAS: JsBarcode 3.11.6 e qrcode-generator 1.4.4, guardados em assets/vendor/ (o hash no CLAUDE.md) e carregados
//  SÓ na hora de imprimir ou de mostrar uma etiqueta. As bibliotecas não desenham nada na página: elas devolvem os módulos
//  (0/1) e o SVG é React puro, com a largura de cada barra em MILÍMETROS — um número inteiro de pontos da térmica
//  (203 dpi: 0,375 mm = 3 pontos). Barra que cai entre pontos sai torta, e o leitor erra.
// ═══════════════════════════════════════════════════════════════════════════

const CB_VENDOR = {
  barras: 'assets/vendor/JsBarcode-3.11.6.all.min.js',
  qr: 'assets/vendor/qrcode-generator-1.4.4.js',
};

// ── As bibliotecas, carregadas uma vez por visita ───────────────────────────
let cbBibliotecasPromessa = null;
function cbCarregarScript(src, pronto) {
  if (pronto()) return Promise.resolve();
  return new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = src;
    s.async = true;
    const relogio = setTimeout(() => reject(new Error('o arquivo ' + src + ' não chegou em 20 segundos')), 20000);
    s.onload = () => {
      clearTimeout(relogio);
      if (!pronto()) { reject(new Error('o arquivo ' + src + ' carregou, mas o gerador não se registrou')); return; }
      resolve();
    };
    s.onerror = () => { clearTimeout(relogio); reject(new Error('o site não entregou ' + src)); };
    document.head.appendChild(s);
  });
}
function cbCarregarBibliotecas() {
  if (window.JsBarcode && window.qrcode) return Promise.resolve();
  if (cbBibliotecasPromessa) return cbBibliotecasPromessa;
  cbBibliotecasPromessa = cbCarregarScript(CB_VENDOR.barras, () => typeof window.JsBarcode === 'function')
    .then(() => cbCarregarScript(CB_VENDOR.qr, () => typeof window.qrcode === 'function'))
    .catch((e) => { cbBibliotecasPromessa = null; throw e; });
  return cbBibliotecasPromessa;
}
function useCbBibliotecas() {
  const [st, setSt] = React.useState(() => ({ pronto: !!(window.JsBarcode && window.qrcode), erro: '' }));
  React.useEffect(() => {
    if (st.pronto) return undefined;
    let vivo = true;
    cbCarregarBibliotecas().then(
      () => { if (vivo) setSt({ pronto: true, erro: '' }); },
      (e) => { if (vivo) setSt({ pronto: false, erro: (e && e.message) || String(e) }); });
    return () => { vivo = false; };
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return st;
}

// ── Módulos e desenho ───────────────────────────────────────────────────────
// A resolução da térmica decide a largura do módulo: um número INTEIRO de pontos. A escolha fica no navegador (é da
// impressora deste computador), uma por tamanho de etiqueta.
const CB_DPI = {
  203: { barra: 0.375, qr: 0.75 },      // 3 e 6 pontos
  300: { barra: 0.3387, qr: 0.6773 },   // 4 e 8 pontos
};
function cbDpiLido(tamanho) {
  try { return window.localStorage.getItem('tm-etq' + tamanho + '-dpi') === '300' ? 300 : 203; } catch (e) { return 203; }
}
function cbDpiGravar(tamanho, v) {
  try { window.localStorage.setItem('tm-etq' + tamanho + '-dpi', String(v)); } catch (e) { /* sem armazenamento: vale só agora */ }
}
function CbDpiSelect({ tamanho, valor, onChange }) {
  return (
    <label className="cb-dpi" title="A largura de cada barra tem que ser um número inteiro de pontos da impressora">
      <span>Impressora {tamanho === '80' ? '80 × 50' : '150 × 100'}:</span>
      <select className="input" value={String(valor)} onChange={e => { const v = Number(e.target.value); cbDpiGravar(tamanho, v); onChange(v); }}>
        <option value="203">203 dpi</option>
        <option value="300">300 dpi</option>
      </select>
    </label>
  );
}

const cbModCache = new Map();
// Os módulos (texto de 0 e 1) de um código, pelo JsBarcode SEM desenhar nada (o "object renderer"). null = inválido.
function cbModulos(formato, valor) {
  if (!valor || typeof window.JsBarcode !== 'function') return null;
  const k = formato + '|' + valor;
  if (cbModCache.has(k)) return cbModCache.get(k);
  let bits = null;
  try {
    const o = {}; let valido = true;
    window.JsBarcode(o, String(valor), { format: formato, flat: true, valid: (v) => { valido = v; } });
    bits = valido && Array.isArray(o.encodings) && o.encodings.length ? o.encodings.map(e => e.data).join('') : null;
  } catch (e) { bits = null; }
  cbModCache.set(k, bits);
  return bits;
}
const cbQrCache = new Map();
function cbQrMatriz(valor) {
  if (!valor || typeof window.qrcode !== 'function') return null;
  if (cbQrCache.has(valor)) return cbQrCache.get(valor);
  let m = null;
  try {
    const qr = window.qrcode(0, 'M');
    qr.addData(String(valor));
    qr.make();
    const n = qr.getModuleCount();
    m = Array.from({ length: n }, (_, r) => Array.from({ length: n }, (_, c) => qr.isDark(r, c)));
  } catch (e) { m = null; }
  cbQrCache.set(valor, m);
  return m;
}

// Barras em SVG: um retângulo por trecho preto, a largura de cada módulo em mm. `silencio` = módulos brancos de cada lado
// (EAN-13: 11 e 7, pela GS1; Code 128: 10).
function CbBarras({ formato, valor, moduloMm, alturaMm, silencioEsq, silencioDir }) {
  const bits = cbModulos(formato, valor);
  if (!bits) {
    return (
      <div className="cb-sem-barra" style={{ height: alturaMm + 'mm' }}>
        {typeof window.JsBarcode === 'function' ? 'código sem barras possíveis' : 'carregando o gerador de barras…'}
      </div>
    );
  }
  const e = silencioEsq != null ? silencioEsq : 10, d = silencioDir != null ? silencioDir : 10;
  const n = bits.length + e + d;
  const rects = [];
  for (let i = 0; i < bits.length;) {
    if (bits[i] === '1') {
      let j = i; while (j < bits.length && bits[j] === '1') j++;
      rects.push(<rect key={i} x={e + i} y={0} width={j - i} height={1} />);
      i = j;
    } else i++;
  }
  return (
    <svg className="cb-svg" width={(n * moduloMm).toFixed(3) + 'mm'} height={alturaMm + 'mm'} viewBox={'0 0 ' + n + ' 1'}
      preserveAspectRatio="none" shapeRendering="crispEdges" role="img" aria-label={'código de barras ' + valor}>
      <rect x={0} y={0} width={n} height={1} fill="#fff" />
      <g fill="#000">{rects}</g>
    </svg>
  );
}
function CbQr({ valor, moduloMm, silencio }) {
  const m = cbQrMatriz(valor);
  const q = silencio != null ? silencio : 4;
  if (!m) return <div className="cb-sem-barra" style={{ width: (29 * moduloMm) + 'mm', height: (29 * moduloMm) + 'mm' }}>QR…</div>;
  const n = m.length, tot = n + 2 * q;
  const rects = [];
  m.forEach((linha, r) => {
    for (let c = 0; c < n;) {
      if (linha[c]) {
        let j = c; while (j < n && linha[j]) j++;
        rects.push(<rect key={r + '-' + c} x={q + c} y={q + r} width={j - c} height={1} />);
        c = j;
      } else c++;
    }
  });
  const lado = (tot * moduloMm).toFixed(3) + 'mm';
  return (
    <svg className="cb-svg" width={lado} height={lado} viewBox={'0 0 ' + tot + ' ' + tot} shapeRendering="crispEdges"
      role="img" aria-label={'QR ' + valor}>
      <rect x={0} y={0} width={tot} height={tot} fill="#fff" />
      <g fill="#000">{rects}</g>
    </svg>
  );
}
// Os dígitos embaixo da barra, como no rótulo: "7 890802 492607" (EAN-13); o resto, inteiro.
function cbDigitos(codigo) {
  const t = eanLimpo(codigo);
  if (/^[0-9]{13}$/.test(t)) return t[0] + ' ' + t.slice(1, 7) + ' ' + t.slice(7);
  if (/^[0-9]{8}$/.test(t)) return t.slice(0, 4) + ' ' + t.slice(4);
  return t;
}

// ── As etiquetas 80 × 50 mm ─────────────────────────────────────────────────
function EtiquetaProduto80({ item, codigo, fonte, dpi }) {
  const m = CB_DPI[dpi] || CB_DPI[203];
  const f = cbFormatoDe(codigo);
  const ean = f.formato !== 'CODE128';
  return (
    <div className="etq80-folha">
      <div className="etq80-prod">
        <div className="etq80-nome">{item.nome || '—'}</div>
        <div className="etq80-lin">
          <span className="etq80-tom">{item.tom || ''}</span>
          <span className="etq80-sku">{item.sku ? 'SKU ' + item.sku : ''}</span>
        </div>
        <div className="etq80-barra">
          <CbBarras formato={f.formato} valor={f.valor} moduloMm={m.barra} alturaMm={16}
            silencioEsq={ean ? 11 : 10} silencioDir={ean ? 7 : 10} />
        </div>
        <div className="etq80-digitos">{cbDigitos(codigo)}</div>
        <div className="etq80-pe">{fonte === 'ean' ? 'EAN do rótulo' : 'código interno'}</div>
      </div>
    </div>
  );
}
function EtiquetaCaixa80({ caixa, dpi }) {
  const m = CB_DPI[dpi] || CB_DPI[203];
  const nome = [caixa.produtoNome, caixa.variacaoNome].filter(Boolean).join(' · ');
  return (
    <div className="etq80-folha">
      <div className="etq80-cx">
        <div className="etq80-cx-topo">
          <CbQr valor={caixa.codigo} moduloMm={m.qr} silencio={2} />
          <div className="etq80-cx-info">
            <div className="etq80-cx-num"><span>CAIXA Nº {caixa.numero}</span><span>{caixa.unidades} UN</span></div>
            <div className="etq80-cx-nome">{nome}</div>
            <div className="etq80-cx-lote">
              LOTE {caixa.lote || '________'} · VAL. {caixa.validade ? fmtDate(caixa.validade) : '__/__/____'}
            </div>
          </div>
        </div>
        {caixa.bloqueio ? <div className="etq80-faixa">■ {String(bloqueioLabel(caixa.bloqueio)).toUpperCase()} — NÃO VENDER</div> : null}
        <div className="etq80-cx-barra">
          <CbBarras formato="CODE128" valor={caixa.codigo} moduloMm={m.barra} alturaMm={12} />
          <span className="etq80-cx-cod">{caixa.codigo}</span>
        </div>
      </div>
    </div>
  );
}
// A etiqueta de TESTE: o QR, o Code 128 com letras e o EAN-13 — os três números que a sequência nunca dá. Ler os três no
// campo de conferência prova a impressora, o papel e a configuração do leitor (teclado, Caps Lock, o Enter no fim).
function EtiquetaTeste80({ dpi }) {
  const m = CB_DPI[dpi] || CB_DPI[203];
  return (
    <div className="etq80-folha">
      <div className="etq80-cx">
        <div className="etq80-cx-topo">
          <CbQr valor={CB_TESTE_CAIXA} moduloMm={m.qr} silencio={2} />
          <div className="etq80-cx-info">
            <div className="etq80-cx-num"><span>ETIQUETA DE TESTE</span><span>{dpi} dpi</span></div>
            <div className="etq80-teste-ean">
              <CbBarras formato="EAN13" valor={CB_TESTE_EAN} moduloMm={m.barra} alturaMm={10} silencioEsq={11} silencioDir={7} />
              <div className="etq80-digitos">{cbDigitos(CB_TESTE_EAN)}</div>
            </div>
          </div>
        </div>
        <div className="etq80-cx-barra">
          <CbBarras formato="CODE128" valor={CB_TESTE_CAIXA} moduloMm={m.barra} alturaMm={11} />
          <span className="etq80-cx-cod">{CB_TESTE_CAIXA}</span>
        </div>
      </div>
    </div>
  );
}

// ── A impressão (o mesmo molde da etiqueta de caixa 150×100) ────────────────
// `folhas`: [{ chave, tipo: 'produto'|'caixa'|'teste', item, codigo, fonte, caixa }]. Só a caixa tem o "saiu?": a
// marca da 80×50 mora no banco (a fila de caixas sem código colado). Etiqueta de produto se reimprime à vontade.
function Etiquetas80Overlay({ folhas, onClose, onConfirmadas }) {
  const libs = useCbBibliotecas();
  const [dpi, setDpi] = React.useState(() => cbDpiLido('80'));
  const [perguntar, setPerguntar] = React.useState(false);
  const [gravando, setGravando] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const caixas = folhas.filter(f => f.tipo === 'caixa').map(f => f.caixa);
  const n = folhas.length;
  const imprimir = () => { window.print(); if (caixas.length) setPerguntar(true); };
  const confirmar = async () => {
    if (window.tmNegadoAqui && window.tmNegadoAqui('criar')) { setErro(window.tmNegadoAqui('criar')); return; }
    setGravando(true); setErro('');
    const r = await tmCaixas.marcarCodigoImpresso(caixas.map(c => c.id));
    setGravando(false);
    if (r && r.error) { setErro(caixaMsgErro(r.error)); return; }
    onConfirmadas && onConfirmadas(caixas.length);
  };
  return ReactDOM.createPortal(
    <div className="doc-print-overlay etq80-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 80 × 50 — {n}</span>
            <CbDpiSelect tamanho="80" valor={dpi} onChange={setDpi} />
            <Button variant="primary" icon="printer" size="sm" onClick={imprimir} disabled={!libs.pronto}>
              {libs.pronto ? `Imprimir ${n} etiqueta${n > 1 ? 's' : ''}` : 'Carregando…'}
            </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">
              {caixas.length > 1 ? `As etiquetas das ${caixas.length} caixas saíram da impressora?` : 'A etiqueta da caixa saiu da impressora?'}
            </span>
            <Button variant="primary" icon="check" size="sm" pede="criar" 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>
        )}
        <div className="cxd-printbar-nota">
          {perguntar
            ? 'Só marco as caixas quando você confirma: o navegador não avisa se a impressão foi cancelada. Respondendo "não", elas continuam na fila.'
            : 'Na impressão: a impressora 80 × 50, papel 80 × 50 mm, margens "Nenhuma", escala 100%. A largura das barras depende da resolução da impressora — escolha a dela ao lado.'}
        </div>
        {libs.erro ? <div className="cl-erro" style={{ marginTop: 8 }}>Não consegui carregar o gerador de código de barras: {libs.erro}. Nada foi impresso. Recarregue a página.</div> : null}
        {erro ? <div className="cl-erro" style={{ marginTop: 8 }}>{erro}</div> : null}
      </div>
      <div className="etq-palco">
        {folhas.map(f => (
          f.tipo === 'caixa' ? <EtiquetaCaixa80 key={f.chave} caixa={f.caixa} dpi={dpi} />
            : f.tipo === 'teste' ? <EtiquetaTeste80 key={f.chave} dpi={dpi} />
              : <EtiquetaProduto80 key={f.chave} item={f.item} codigo={f.codigo} fonte={f.fonte} dpi={dpi} />
        ))}
      </div>
    </div>,
    document.body
  );
}

// ── Ler um código para IDENTIFICAR (nada grava) ─────────────────────────────
// O leitor USB é um teclado: digita o código e manda Enter. Este campo só diz o que o código É.
function CbLeituraConferir({ indice, onAchou, rotulo }) {
  const [texto, setTexto] = React.useState('');
  const [lidas, setLidas] = React.useState([]);
  const ref = React.useRef(null);
  const ler = async (bruto) => {
    const r = cbResolverLeitura(bruto, indice);
    if (r.tipo === 'vazio') return;
    let linha = { ...r, quando: new Date().toLocaleTimeString('pt-BR') };
    if (r.tipo === 'caixa') {
      const c = await tmCaixas.porCodigo(r.codigo);
      linha = { ...linha, caixa: c && c.caixa, erroLeitura: c && c.error ? caixaMsgErro(c.error) : '' };
    }
    setLidas(ls => [linha].concat(ls).slice(0, 6));
    if (onAchou) onAchou(linha);
  };
  const tecla = (e) => {
    if (e.key === 'Enter' || (e.key === 'Tab' && texto)) {
      e.preventDefault();
      const t = texto; setTexto('');
      ler(t);
    }
  };
  return (
    <div className="cb-leitura">
      <label className="cb-leitura-campo">
        <Icon name="scan-line" size={18} />
        <input ref={ref} className="input" value={texto} onChange={e => setTexto(e.target.value)} onKeyDown={tecla}
          placeholder={rotulo || 'Clique aqui e leia com o leitor (ou digite e tecle Enter)'} autoComplete="off" spellCheck={false} />
      </label>
      <div className="cb-leitura-nota">Só identifica — nada aqui grava nem mexe em estoque.</div>
      {lidas.map((l, i) => <CbLeituraLinha key={i + l.quando + l.lido} l={l} />)}
    </div>
  );
}
function CbLeituraLinha({ l }) {
  const nomeItem = (it) => [it.sku, it.nome, it.tom].filter(Boolean).join(' · ');
  let tom = 'erro', txt;
  if (l.tipo === 'teste') {
    tom = 'ok'; txt = <>Etiqueta de teste lida: <b className="cb-mono">{l.lido}</b>. A impressora e o leitor estão combinando.</>;
  } else if (l.tipo === 'itens' && l.itens.length === 1) {
    const it = l.itens[0];
    tom = 'ok'; txt = <><b>{nomeItem(it)}</b> — {eanLimpo(it.ean) && eanValido(it.ean) && eanLimpo(it.ean).padStart(14, '0') === l.lido.padStart(14, '0') ? 'EAN do rótulo' : 'código interno'} <span className="cb-mono">{l.lido}</span></>;
  } else if (l.tipo === 'itens') {
    tom = 'warn'; txt = <>O código <b className="cb-mono">{l.lido}</b> está em {l.itens.length} itens: {l.itens.map(nomeItem).join('; ')}. É um EAN repetido — o dono se escolhe em Produtos › Códigos de barras.</>;
  } else if (l.tipo === 'caixa') {
    if (l.caixa) {
      tom = 'ok'; txt = <>Caixa <b>Nº {l.caixa.numero}</b> · {[l.caixa.produtoNome, l.caixa.variacaoNome].filter(Boolean).join(' · ')} · {l.caixa.unidades} un ao fechar{l.caixa.esvaziada ? ' · esvaziada' : ''} · <span className="cb-mono">{l.lido}</span></>;
    } else if (l.erroLeitura) {
      txt = <>Não consegui ler a caixa <span className="cb-mono">{l.lido}</span>: {l.erroLeitura}</>;
    } else {
      txt = <>Nenhuma caixa com o código <b className="cb-mono">{l.lido}</b> (pode ter sido excluída). Nada foi gravado.</>;
    }
  } else if (l.tipo === 'invalido') {
    txt = <><b className="cb-mono">{l.lido}</b>: o dígito verificador não confere — não é um EAN válido (leitura errada, ou etiqueta danificada).</>;
  } else {
    txt = <><b className="cb-mono">{l.lido}</b> não é de nenhum produto, tom ou caixa do cadastro. Nada foi gravado.</>;
  }
  return (
    <div className={'cl-aviso ' + (tom === 'ok' ? 'ok' : tom === 'warn' ? 'warn' : 'erro') + ' cb-leitura-linha'}>
      <Icon name={tom === 'ok' ? 'check-circle' : tom === 'warn' ? 'alert-triangle' : 'x-circle'} size={14} />
      <span>{txt}{l.minusculas ? <> <i>O leitor mandou letras minúsculas: o Caps Lock está ligado? (o app aceita)</i></> : null}</span>
      <span className="cb-leitura-hora">{l.quando}</span>
    </div>
  );
}

// ── A linha do código no cadastro (produto e tonalidade) ────────────────────
// Diz a situação do EAN que está no campo AGORA (o leitor digita nele) e, sem código de leitura, oferece gerar o interno.
function CbEanLinha({ item, indice, podeGerar, onGerar }) {
  const [gerando, setGerando] = React.useState(false);
  const [erro, setErro] = React.useState('');
  const sit = cbSituacao(item, indice);
  const nomeItem = (it) => [it.sku, it.nome, it.tom].filter(Boolean).join(' · ');
  const gerar = async () => {
    setGerando(true); setErro('');
    const r = await onGerar();
    setGerando(false);
    if (r && r.error) setErro(r.error.message || String(r.error));
  };
  let msg, tom;
  if (sit.situacao === 'ean') { tom = 'ok'; msg = 'EAN válido — é o código de leitura deste item.'; }
  else if (sit.situacao === 'invalido') { tom = 'erro'; msg = 'O dígito verificador não confere: leia o código no frasco com o leitor (clique no campo e leia). Com o EAN assim, a troca não salva.'; }
  else if (sit.situacao === 'repetido') { tom = 'warn'; msg = 'Este EAN também está em ' + sit.outros.map(nomeItem).join('; ') + '. O dono se escolhe em Produtos › Códigos de barras, lendo o frasco.'; }
  else { tom = 'neutro'; msg = 'Sem EAN. O campo aceita o leitor: clique nele e leia o frasco.'; }
  return (
    <div className={'cb-ean-linha ' + tom}>
      <span>{msg}</span>
      {item.codigoInterno
        ? <span className="cb-ean-int">código interno <b className="cb-mono">{item.codigoInterno}</b>{sit.fonte === 'interno' ? ' (é o da etiqueta)' : ''}</span>
        : (sit.situacao !== 'ean' && podeGerar
          ? <Button size="sm" variant="secondary" icon="barcode" pede="editar" onClick={gerar} disabled={gerando}>{gerando ? 'Gerando…' : 'Gerar código interno'}</Button>
          : null)}
      {erro ? <span className="cb-ean-erro">{erro}</span> : null}
    </div>
  );
}

// ── A tela: Produtos › Códigos de barras ────────────────────────────────────
function CodigosBarras({ onNav }) {
  const [cat, setCat] = React.useState(null);          // { produtos, variacoes } | { error }
  const [reloadKey, setReloadKey] = React.useState(0);
  const [aviso, setAviso] = React.useState(null);     // { tom, texto }
  const [ocupado, setOcupado] = React.useState('');
  const [foco, setFoco] = React.useState('');          // o EAN do grupo lido no frasco
  const [verSem, setVerSem] = React.useState(false);

  React.useEffect(() => {
    let vivo = true;
    tmDb.lerCatalogoCodigos().then((r) => { if (vivo) setCat(r || { error: new Error('sem resposta') }); });
    return () => { vivo = false; };
  }, [reloadKey]);

  const ok = !!(cat && !cat.error);
  const itens = React.useMemo(() => (ok ? cbItensDoCatalogo(cat.produtos, cat.variacoes) : []), [cat, ok]);
  const indice = React.useMemo(() => cbIndice(itens), [itens]);
  const conf = React.useMemo(() => cbConferencia(itens, indice), [itens, indice]);
  const nomeItem = (it) => [it.sku, it.nome, it.tom].filter(Boolean).join(' · ');
  const recarregar = () => setReloadKey(k => k + 1);

  const definirDono = async (grupo, it) => {
    const outros = grupo.itens.filter(x => x.chave !== it.chave);
    if (!window.confirm(`${nomeItem(it)} fica com o EAN ${grupo.ean}.\n\n${outros.map(nomeItem).join('\n')}\n\n${outros.length > 1 ? 'perdem' : 'perde'} o EAN e ${outros.length > 1 ? 'ganham' : 'ganha'} um código interno (para a etiqueta). Confirmar?`)) return;
    setOcupado(grupo.gtin); setAviso(null);
    const r = await tmDb.definirDonoEan(grupo.ean, { produtoId: it.produtoId, variacaoId: it.variacaoId });
    setOcupado('');
    if (r.error) { setAviso({ tom: 'erro', texto: 'Nada foi gravado: ' + (r.error.message || r.error) }); return; }
    setAviso({ tom: 'ok', texto: `${nomeItem(it)} ficou com o EAN ${grupo.ean}. ` + r.tirados.map(t => `${[t.sku, t.nome].filter(Boolean).join(' · ')} ganhou o código ${t.codigo}`).join('; ') + '.' });
    recarregar();
  };
  const gerarTodos = async () => {
    const alvo = conf.semCodigo;
    if (!alvo.length) return;
    if (!window.confirm(`Gerar o código interno de ${alvo.length} ite${alvo.length > 1 ? 'ns' : 'm'} sem EAN?\n\nO código é para sempre: depois de impresso, ele não muda. Os EANs repetidos e os com o dígito errado ficam de fora — eles esperam o frasco.`)) return;
    setOcupado('todos'); setAviso(null);
    let feitos = 0;
    for (let i = 0; i < alvo.length; i += 500) {
      const lote = alvo.slice(i, i + 500);
      const r = await tmDb.gerarCodigoInterno({
        produtos: lote.filter(x => x.tipo === 'produto').map(x => x.produtoId),
        variacoes: lote.filter(x => x.tipo === 'tom').map(x => x.variacaoId),
      });
      if (r.error) {
        setOcupado('');
        setAviso({ tom: 'erro', texto: `${feitos} código${feitos === 1 ? '' : 's'} gerado${feitos === 1 ? '' : 's'}; o resto NÃO: ${r.error.message || r.error}. Tentar de novo é seguro: quem já tem código não muda.` });
        recarregar(); return;
      }
      feitos += r.gerados.length;
    }
    setOcupado('');
    setAviso({ tom: 'ok', texto: `${feitos} código${feitos === 1 ? '' : 's'} interno${feitos === 1 ? '' : 's'} gerado${feitos === 1 ? '' : 's'}. As etiquetas se imprimem em Estoque › Etiquetas de código.` });
    recarregar();
  };
  const achou = (l) => {
    if (l.tipo === 'itens' && l.itens.length > 1) setFoco(String(l.lido).padStart(14, '0'));
  };

  const carregando = cat === null || !ok;
  return (
    <div className="canvas cb-tela">
      <PageHead title="Códigos de barras"
        sub="O EAN do rótulo quando ele existe e é só daquele item; para os outros, o código interno do app (200…). Nada aqui mexe em estoque."
        right={<Button variant="secondary" icon="tags" onClick={() => onNav && onNav('estoque/etiquetas')}>Etiquetas de código</Button>} />

      {aviso ? <div className={'cl-aviso ' + (aviso.tom === 'ok' ? 'ok' : 'erro')} style={{ marginBottom: 14 }}><Icon name={aviso.tom === 'ok' ? 'check-circle' : 'x-circle'} size={14} /><span>{aviso.texto}</span></div> : null}
      {cat && cat.error ? <div className="cl-erro" role="alert">Não consegui ler o catálogo ({cat.error.message || String(cat.error)}). Nada abaixo vale até a leitura dar certo — recarregue a página.</div> : null}

      <div className="cb-resumo">
        <div><b>{carregando ? '…' : conf.comCodigo}</b><span>com código de leitura, de {carregando ? '…' : conf.total} itens (produtos e tons)</span></div>
        <div><b>{carregando ? '…' : conf.repetidos.length}</b><span>EAN repetido{conf.repetidos.length === 1 ? '' : 's'}</span></div>
        <div><b>{carregando ? '…' : conf.invalidos.length}</b><span>com o dígito errado</span></div>
        <div><b>{carregando ? '…' : conf.semCodigo.length}</b><span>sem EAN e sem código</span></div>
      </div>

      <Card title="Ler um frasco">
        <div className="card-pad">
          <CbLeituraConferir indice={indice} onAchou={achou} rotulo="Clique aqui e leia o código do frasco com o leitor" />
        </div>
      </Card>

      <Card title="EAN repetido — escolha o dono lendo o frasco">
        <div className="card-pad">
          {conf.repetidos.length === 0
            ? <div className="muted">Nenhum EAN repetido.</div>
            : <>
              <div className="cb-dica">O dono é o item cujo frasco traz aquele número. Leia o frasco no campo acima: o grupo se destaca aqui. O dono fica com o EAN; os outros perdem o EAN e ganham o código interno — e o EAN certo deles se lê depois, no frasco de cada um, no cadastro.</div>
              {conf.repetidos.map(g => (
                <div key={g.gtin} className={'cb-grupo' + (foco === g.gtin ? ' foco' : '')}>
                  <div className="cb-grupo-ean cb-mono">{g.ean}</div>
                  {g.itens.map(it => (
                    <div key={it.chave} className="cb-grupo-item">
                      <span>{nomeItem(it)}{it.tipo === 'tom' ? <Badge tone="neutral">tom</Badge> : null}</span>
                      <Button size="sm" variant="secondary" icon="check" pede="editar" disabled={!!ocupado} onClick={() => definirDono(g, it)}>
                        {ocupado === g.gtin ? 'Gravando…' : 'Este é o dono'}
                      </Button>
                    </div>
                  ))}
                </div>
              ))}
            </>}
        </div>
      </Card>

      <Card title="EAN com o dígito errado — o leitor não reconhece">
        <div className="card-pad">
          {conf.invalidos.length === 0
            ? <div className="muted">Nenhum.</div>
            : <>
              <div className="cb-dica">O último dígito (o verificador) não bate com os outros: foi digitado errado. Abra o produto em Produtos › Lista, clique no campo EAN e leia o frasco. Até lá, a leitura deste número não acha nada.</div>
              <table className="tbl"><tbody>
                {conf.invalidos.map(it => (
                  <tr key={it.chave}><td>{nomeItem(it)}</td><td className="cb-mono">{it.ean}</td></tr>
                ))}
              </tbody></table>
            </>}
        </div>
      </Card>

      <Card title="Sem EAN e sem código">
        <div className="card-pad">
          {conf.semCodigo.length === 0
            ? <div className="muted">Todo item sem EAN já tem código interno.</div>
            : <>
              <div className="cb-dica">Antes de gerar, vale ler os frascos: produto com código de barras no rótulo usa o EAN dele. O código interno é para quem não tem (e para o kit e o frasco sem rótulo). Os repetidos e os com o dígito errado não entram aqui: eles esperam o frasco.</div>
              <div className="cb-acoes">
                <Button variant="primary" icon="barcode" pede="editar" disabled={!!ocupado || carregando} onClick={gerarTodos}>
                  {ocupado === 'todos' ? 'Gerando…' : `Gerar código para os ${conf.semCodigo.length}`}
                </Button>
                <button type="button" className="cxd-link" onClick={() => setVerSem(v => !v)}>{verSem ? 'esconder a lista' : 'ver a lista'}</button>
              </div>
              {verSem ? (
                <table className="tbl" style={{ marginTop: 10 }}><tbody>
                  {conf.semCodigo.map(it => (
                    <tr key={it.chave}><td>{nomeItem(it)}</td>
                      <td>{it.ehKit ? <Badge tone="neutral">kit</Badge> : it.insumo ? <Badge tone="neutral">insumo</Badge> : it.tipo === 'tom' ? <Badge tone="neutral">tom</Badge> : null}</td></tr>
                  ))}
                </tbody></table>
              ) : null}
            </>}
        </div>
      </Card>
    </div>
  );
}

// ── A fila das etiquetas de produto (na aba: é uma lista de trabalho, não um registro) ──
function cbFilaLer() {
  try { const v = JSON.parse(window.sessionStorage.getItem('tm-etq80-fila') || '[]'); return Array.isArray(v) ? v.filter(x => x && x.chave) : []; } catch (e) { return []; }
}
function cbFilaGravar(fila) {
  try { window.sessionStorage.setItem('tm-etq80-fila', JSON.stringify(fila)); } catch (e) { /* sem armazenamento: vale enquanto a tela estiver aberta */ }
}

// ── A tela: Estoque › Etiquetas de código ───────────────────────────────────
function EtiquetasCodigo({ onNav }) {
  const [cat, setCat] = React.useState(null);          // { produtos, variacoes } | { error }
  const [caixas, setCaixas] = React.useState(null);
  const [erroCx, setErroCx] = React.useState('');
  const [reloadKey, setReloadKey] = React.useState(0);
  const [fila, setFilaEstado] = React.useState(() => cbFilaLer());
  const [escolha, setEscolha] = React.useState('');
  const [qtd, setQtd] = React.useState('1');
  const [todasCaixas, setTodasCaixas] = React.useState(false);
  // ids das caixas escolhidas. Nascem DESMARCADAS (26/10/2026, Lote 0 da UX): antes, null queria dizer "todas as da fila",
  // e imprimir UMA etiqueta de produto mandava junto as 72 caixas sem o código colado. Quem quer as caixas marca (ou
  // "marcar as N"), e o botão diz quantas vão.
  const [marcadas, setMarcadas] = React.useState(null);   // null = nenhuma escolhida ainda
  const [imprimindo, setImprimindo] = React.useState(null);
  const [aviso, setAviso] = React.useState(null);
  const [gerando, setGerando] = React.useState('');
  const setFila = (f) => { setFilaEstado(f); cbFilaGravar(f); };

  React.useEffect(() => {
    let vivo = true;
    (async () => {
      const [c, cx] = await Promise.all([tmDb.lerCatalogoCodigos(), tmCaixas.naoEsvaziadas()]);
      if (!vivo) return;
      setCat(c || { error: new Error('sem resposta') });
      setCaixas((cx && cx.caixas) || []);
      setErroCx(cx && cx.error ? caixaMsgErro(cx.error) : '');
    })();
    return () => { vivo = false; };
  }, [reloadKey]);

  const catOk = !!(cat && !cat.error);
  const itens = React.useMemo(() => (catOk ? cbItensDoCatalogo(cat.produtos, cat.variacoes) : []), [cat, catOk]);
  const indice = React.useMemo(() => cbIndice(itens), [itens]);
  const porChave = React.useMemo(() => { const m = {}; itens.forEach(it => { m[it.chave] = it; }); return m; }, [itens]);
  const nomeItem = (it) => [it.sku, it.nome, it.tom].filter(Boolean).join(' · ');
  const opcoes = React.useMemo(() => itens.filter(it => !it.temTons || it.tipo === 'tom')
    .map(it => ({ value: it.chave, label: nomeItem(it) })), [itens]);

  const linhas = fila.map(f => {
    const it = porChave[f.chave];
    const sit = it ? cbSituacao(it, indice) : null;
    return { ...f, it, sit };
  });
  const caixasFila = (caixas || []).filter(c => c.semCodigoColado);
  const caixasVisiveis = todasCaixas ? (caixas || []) : caixasFila;
  const escolhidas = marcadas === null ? new Set() : marcadas;
  const caixasEscolhidas = caixasVisiveis.filter(c => escolhidas.has(c.id));
  const nProdutos = linhas.reduce((a, l) => a + (l.sit && l.sit.codigo ? (parseInt(l.qtd, 10) || 0) : 0), 0);

  const adicionar = () => {
    const n = Math.max(1, Math.min(500, parseInt(qtd, 10) || 1));
    if (!escolha) return;
    const ja = fila.find(f => f.chave === escolha);
    setFila(ja ? fila.map(f => f.chave === escolha ? { ...f, qtd: (parseInt(f.qtd, 10) || 0) + n } : f) : fila.concat([{ chave: escolha, qtd: n }]));
    setEscolha(''); setQtd('1');
  };
  const umDeCada = () => {
    const ja = new Set(fila.map(f => f.chave));
    const novos = itens.filter(it => (!it.temTons || it.tipo === 'tom') && !it.insumo && !ja.has(it.chave) && cbSituacao(it, indice).codigo)
      .map(it => ({ chave: it.chave, qtd: 1 }));
    setFila(fila.concat(novos));
    setAviso({ tom: 'ok', texto: `${novos.length} ite${novos.length === 1 ? 'm' : 'ns'} com código entraram na fila (1 de cada; insumos de fora).` });
  };
  const gerarPara = async (l) => {
    setGerando(l.chave); setAviso(null);
    const r = await tmDb.gerarCodigoInterno(l.it.tipo === 'tom' ? { variacoes: [l.it.variacaoId] } : { produtos: [l.it.produtoId] });
    setGerando('');
    if (r.error) { setAviso({ tom: 'erro', texto: 'Nada foi gravado: ' + (r.error.message || r.error) }); return; }
    setReloadKey(k => k + 1);
  };
  const imprimirTudo = () => {
    const folhas = [];
    linhas.forEach(l => {
      if (!l.sit || !l.sit.codigo) return;
      const n = Math.max(0, Math.min(500, parseInt(l.qtd, 10) || 0));
      for (let i = 0; i < n; i++) folhas.push({ chave: l.chave + '#' + i, tipo: 'produto', item: l.it, codigo: l.sit.codigo, fonte: l.sit.fonte });
    });
    caixasEscolhidas.forEach(c => folhas.push({ chave: 'cx:' + c.id, tipo: 'caixa', caixa: c }));
    if (folhas.length) setImprimindo(folhas);
  };
  const marcar = (id) => {
    const s = new Set(escolhidas);
    if (s.has(id)) s.delete(id); else s.add(id);
    setMarcadas(s);
  };

  return (
    <div className="canvas cb-tela">
      <PageHead title="Etiquetas de código (80 × 50 mm)"
        sub="Produto na prateleira (ou frasco sem rótulo) e caixa do depósito. Imprimir não grava nada — só a caixa ganha a marca de que a etiqueta saiu."
        right={<>
          <Button variant="secondary" icon="flask-conical" onClick={() => setImprimindo([{ chave: 'teste', tipo: 'teste' }])}>Etiqueta de teste</Button>
          <Button variant="secondary" icon="barcode" onClick={() => onNav && onNav('produtos/codigos')}>Códigos de barras</Button>
        </>} />

      {aviso ? <div className={'cl-aviso ' + (aviso.tom === 'ok' ? 'ok' : 'erro')} style={{ marginBottom: 14 }}><Icon name={aviso.tom === 'ok' ? 'check-circle' : 'x-circle'} size={14} /><span>{aviso.texto}</span></div> : null}
      {cat && cat.error ? <div className="cl-erro" role="alert">Não consegui ler o catálogo ({cat.error.message || String(cat.error)}): os códigos dos itens não aparecem até a leitura dar certo — recarregue a página.</div> : null}

      <Card title="Produtos e tons">
        <div className="card-pad">
          <div className="cb-add">
            <div style={{ flex: 1, minWidth: 0 }}>
              <SearchableSelect label="Produto ou tom" value={escolha} onChange={setEscolha} options={opcoes} placeholder="Busque pelo SKU ou pelo nome" />
            </div>
            <div style={{ width: 110 }}>
              <Field label="Quantas">
                <input className="input" type="number" min="1" step="1" value={qtd} onChange={e => setQtd(e.target.value)} />
              </Field>
            </div>
            <Button variant="secondary" icon="plus" onClick={adicionar} disabled={!escolha}>Pôr na fila</Button>
            <Button variant="ghost" onClick={umDeCada}>Uma de cada (os que têm código)</Button>
          </div>
          {linhas.length === 0 ? <div className="muted" style={{ marginTop: 10 }}>A fila está vazia.</div> : (
            <table className="tbl" style={{ marginTop: 10 }}>
              <thead><tr><th>Item</th><th>Código</th><th className="num right" style={{ width: 90 }}>Quantas</th><th style={{ width: 40 }}></th></tr></thead>
              <tbody>
                {linhas.map(l => (
                  <tr key={l.chave}>
                    <td>{l.it ? nomeItem(l.it) : <span className="muted">item que não existe mais</span>}</td>
                    <td>{l.sit && l.sit.codigo
                      ? <><span className="cb-mono">{l.sit.codigo}</span> <span className="muted">{l.sit.fonte === 'ean' ? 'EAN do rótulo' : 'interno'}</span></>
                      : l.it ? (
                        <span className="cb-sem">
                          {l.sit.situacao === 'repetido' ? 'EAN repetido — escolha o dono' : l.sit.situacao === 'invalido' ? 'EAN com o dígito errado' : 'sem código'}
                          <Button size="sm" variant="secondary" icon="barcode" pede="editar" disabled={!!gerando} onClick={() => gerarPara(l)}>{gerando === l.chave ? 'Gerando…' : 'Gerar código'}</Button>
                        </span>) : null}
                    </td>
                    <td className="num right">
                      <input className="input cb-qtd" type="number" min="0" step="1" value={l.qtd}
                        onChange={e => setFila(fila.map(f => f.chave === l.chave ? { ...f, qtd: e.target.value } : f))} />
                    </td>
                    <td><button type="button" className="cxd-ico" title="Tirar da fila" onClick={() => setFila(fila.filter(f => f.chave !== l.chave))}><Icon name="x" size={14} /></button></td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
          {linhas.length ? <div className="cb-acoes"><button type="button" className="cxd-link" onClick={() => setFila([])}>esvaziar a fila</button></div> : null}
        </div>
      </Card>

      <Card title="Caixas do depósito">
        <div className="card-pad">
          {erroCx ? <div className="cl-erro">Não consegui ler as caixas: {erroCx}</div> : null}
          <div className="cb-dica">
            {caixas === null ? 'Carregando…' : <>
              <b>{caixasFila.length}</b> caixa{caixasFila.length === 1 ? '' : 's'} sem o código colado — a etiqueta grande delas saiu antes de existir o código. Cole uma 80 × 50 ao lado, ou reimprima a grande (150 × 100), que agora traz o código, em Estoque › Caixas do depósito.
            </>}
          </div>
          <label className="cb-check"><input type="checkbox" checked={todasCaixas} onChange={e => { setTodasCaixas(e.target.checked); setMarcadas(null); }} /> mostrar todas as caixas não esvaziadas (para reimprimir)</label>
          {caixasVisiveis.length ? (
            <div className="cb-acoes">
              <button type="button" className="cxd-link" onClick={() => setMarcadas(new Set(caixasVisiveis.map(c => c.id)))}>marcar as {caixasVisiveis.length}</button>
              {escolhidas.size ? <> · <button type="button" className="cxd-link" onClick={() => setMarcadas(null)}>desmarcar todas</button></> : null}
            </div>
          ) : null}
          {caixasVisiveis.length ? (
            <table className="tbl" style={{ marginTop: 8 }}>
              <thead><tr><th style={{ width: 34 }}></th><th>Código</th><th>Caixa</th><th>Produto</th><th className="num right">Un.</th><th>80 × 50</th></tr></thead>
              <tbody>
                {caixasVisiveis.map(c => (
                  <tr key={c.id}>
                    <td><input type="checkbox" checked={escolhidas.has(c.id)} onChange={() => marcar(c.id)} aria-label={'escolher a caixa ' + c.codigo} /></td>
                    <td className="cb-mono">{c.codigo}</td>
                    <td>Nº {c.numero}</td>
                    <td>{[c.produtoSku, c.produtoNome, c.variacaoNome].filter(Boolean).join(' · ')}</td>
                    <td className="num right">{c.unidades}</td>
                    <td>{c.codigo_impresso_em ? <span className="muted">impressa {new Date(c.codigo_impresso_em).toLocaleDateString('pt-BR')}</span> : c.semCodigoColado ? <Badge tone="warning">sem código colado</Badge> : <span className="muted">o código está na grande</span>}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          ) : (caixas !== null ? <div className="muted" style={{ marginTop: 8 }}>Nenhuma.</div> : null)}
        </div>
      </Card>

      <div className="cb-imprimir">
        <span>{nProdutos} de produto · {caixasEscolhidas.length} de caixa</span>
        <Button variant="primary" icon="printer" disabled={nProdutos + caixasEscolhidas.length === 0} onClick={imprimirTudo}>
          Imprimir {nProdutos + caixasEscolhidas.length} etiqueta{nProdutos + caixasEscolhidas.length === 1 ? '' : 's'}
        </Button>
      </div>

      <Card title="Conferir uma etiqueta impressa">
        <div className="card-pad">
          <CbLeituraConferir indice={indice} rotulo="Clique aqui e leia a etiqueta com o leitor" />
        </div>
      </Card>

      {imprimindo ? (
        <Etiquetas80Overlay folhas={imprimindo} onClose={() => setImprimindo(null)}
          onConfirmadas={(n) => { setImprimindo(null); setMarcadas(null); setAviso({ tom: 'ok', texto: `${n} caixa${n === 1 ? '' : 's'} com a etiqueta 80 × 50 marcada como impressa.` }); setReloadKey(k => k + 1); }} />
      ) : null}
    </div>
  );
}

Object.assign(window, {
  cbCarregarBibliotecas, useCbBibliotecas, cbModulos, cbQrMatriz, cbDigitos, cbDpiLido, CB_DPI, CB_VENDOR,
  CbBarras, CbQr, CbDpiSelect, EtiquetaProduto80, EtiquetaCaixa80, EtiquetaTeste80, Etiquetas80Overlay,
  CbLeituraConferir, CbEanLinha, CodigosBarras, EtiquetasCodigo,
});
