/* Panofo Estoque — shell (iteração 3): Mapa · Tabela · Cadastros, fluxos, undo, tweaks */

const { useState, useEffect, useRef } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "conexao": "online",
  "codigos": true
}/*EDITMODE-END*/;

const UNDO_MS = 10000;
const SESSAO_MS = 20 * 60 * 1000; // "terminal destravado" por 20min de inatividade — estilo POS
const STAGE_DIM = { mapa: { w: 1280, h: 800 }, tabela: { w: 1440, h: 900 }, cadastros: { w: 1440, h: 900 }, editorMapa: { w: 1440, h: 900 } };

function horaAgora() {
  const d = new Date();
  return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0');
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const online = t.conexao === 'online';

  // ---- sessão de terminal (destrava com PIN, some sozinha por inatividade) ----
  // Guardado só em memória (nunca localStorage) — some ao fechar a aba. É a
  // ÚNICA fonte de verdade de "quem está usando este tablet agora" — os
  // botões (Mover/Entrada/Ajuste/Cadastros/Editor de Mapa) refletem o papel
  // real de quem autenticou por último, não uma role simulada fixa. Cada
  // ação continua validando o PIN de novo no servidor (RPC é a autoridade
  // real) — isso aqui só decide o que a UI OFERECE, não decide permissão.
  const [sessao, setSessao] = useState(null); // { operador: {id,nome,papel}, pin, expiresAt }
  const [pinGate, setPinGate] = useState(null); // { papeis, motivo, onOk } quando precisa mostrar o PIN
  const sessaoAtiva = sessao && sessao.expiresAt > Date.now();
  const papel = sessaoAtiva ? sessao.operador.papel : null;

  const canWrite = online;
  const canMover = online && (papel === 'gestor' || papel === 'estoquista');
  const podeEntrada = papel === 'gestor' || papel === 'escritorio' || papel === 'estoquista';
  const podeAjuste = papel === 'gestor' || papel === 'escritorio';
  const podeCadastros = papel === 'gestor';

  // Passadas pras telas com PIN próprio (Cadastros/Editor de Mapa/Entrada/
  // Saída/Ajuste) — se já existe sessão de terminal válida com papel
  // compatível, elas pulam o próprio passo de PIN em vez de pedir de novo.
  const sessaoParaGestor = sessaoAtiva && papel === 'gestor' ? sessao : null;
  const sessaoParaEntrada = sessaoAtiva && (papel === 'gestor' || papel === 'escritorio' || papel === 'estoquista') ? sessao : null;
  const sessaoParaAjuste = sessaoAtiva && (papel === 'gestor' || papel === 'escritorio') ? sessao : null;
  const sessaoParaSaida = sessaoAtiva ? sessao : null;

  function estabelecerSessao(op, pin) {
    setSessao({ operador: op, pin, expiresAt: Date.now() + SESSAO_MS });
  }

  const [db, setDb] = useState(null);
  const [loadErro, setLoadErro] = useState(null);
  const [view, setView] = useState(papel === 'escritorio' ? 'tabela' : 'mapa'); // mapa | tabela | cadastros
  const [gal, setGal] = useState(null);
  const galIds = db ? Object.keys(db.galpoes).map(Number).sort((a, b) => a - b) : [];

  useEffect(() => {
    ESTQ.loadAll()
      .then((inicial) => { setDb(JSON.parse(JSON.stringify(inicial))); })
      .catch((err) => setLoadErro(err.message));
  }, []);

  useEffect(() => {
    if (db && gal === null) setGal(galIds[0]);
  }, [db]);
  const [subtab, setSubtab] = useState('mapa');
  const [sheet, setSheet] = useState(null);
  const [detail, setDetail] = useState(null);
  const [mover, setMover] = useState(null);
  const [placing, setPlacing] = useState(null);
  const [saida, setSaida] = useState(null);
  const [ajuste, setAjuste] = useState(null);
  const [entradaOpen, setEntradaOpen] = useState(false);
  const [entradaPick, setEntradaPick] = useState(null);
  const [pickingEntrada, setPickingEntrada] = useState(null);
  const [toast, setToast] = useState(null);
  const [justMoved, setJustMoved] = useState(null);
  const [shake, setShake] = useState(null);
  const toastTimer = useRef(null);
  const stageEl = useRef(null);
  const prevPapel = useRef(papel);

  function refetch() {
    return ESTQ.loadAll().then((inicial) => { setDb(JSON.parse(JSON.stringify(inicial))); });
  }
  function lockSessao() { setSessao(null); }
  // Garante um operador autorizado antes de rodar `onOk(operador, pin)`.
  // Reusa a sessão destravada se ainda válida; senão pede o PIN.
  function garantirOperador(papeis, motivo, onOk) {
    if (sessao && sessao.expiresAt > Date.now() && (!papeis || papeis.includes(sessao.operador.papel))) {
      setSessao((s) => (s ? Object.assign({}, s, { expiresAt: Date.now() + SESSAO_MS }) : s));
      onOk(sessao.operador, sessao.pin);
      return;
    }
    setPinGate({ papeis, motivo, onOk });
  }

  const dim = STAGE_DIM[view];

  // escritório trabalha de desktop → cai na Tabela ao assumir o papel
  useEffect(() => {
    if (papel !== prevPapel.current) {
      if (papel === 'escritorio' && view === 'mapa') setView('tabela');
      if (!podeCadastros && (view === 'cadastros' || view === 'editorMapa')) setView('tabela');
      prevPapel.current = papel;
    }
  });

  // escala do stage
  useEffect(() => {
    function fit() {
      const el = stageEl.current;
      if (!el) return;
      const s = Math.min(window.innerWidth / dim.w, window.innerHeight / dim.h);
      if (!isFinite(s) || s <= 0) { requestAnimationFrame(fit); return; }
      // no tablet (tela ~1280x800) isso nunca passa de 1x; em monitor de PC
      // deixa crescer bem mais (até 1.6x) pra não sobrar tanto preto ao redor
      el.style.transform = 'scale(' + Math.min(s, 1.6) + ')';
    }
    fit();
    window.addEventListener('resize', fit);
    const ro = new ResizeObserver(fit);
    ro.observe(document.documentElement);
    return () => { window.removeEventListener('resize', fit); ro.disconnect(); };
    // `db` entra nas deps de propósito: enquanto carrega, a tela de
    // "Carregando…" não renderiza `.stage`, então `stageEl.current` fica
    // null nessa primeira passada e o efeito nunca reaplicava a escala
    // depois que os dados chegavam — só funcionava por acidente se a
    // janela fosse redimensionada manualmente depois.
  }, [dim.w, dim.h, db]);

  // ---- toast / undo ----
  function showToast(msg, undoDb, info) {
    clearTimeout(toastTimer.current);
    const k = Date.now();
    setToast({ msg, undoDb, k, info: !!info });
    toastTimer.current = setTimeout(() => setToast(null), UNDO_MS);
  }
  function undo() {
    if (toast && toast.undoDb) setDb(toast.undoDb);
    clearTimeout(toastTimer.current);
    setToast(null);
    setJustMoved(null);
  }

  // ---- helpers ----
  function snapshot() { return JSON.parse(JSON.stringify(db)); }
  function findCell(d, g, code) { return d.galpoes[g].cells.find((c) => c.code === code); }
  function palLabel(id, d) {
    const pal = (d || db).pallets[id];
    return (ESTQ.PRODUTOS[pal.prod] || d.produtos[pal.prod]).curto + (pal.lote ? ' (lote ' + pal.lote + ')' : '');
  }
  function logMov(d, id, mov) {
    const pal = d.pallets[id];
    const prev = d.movs[id] || [{ tipo: 'entrada', quem: '—', quando: pal.entradaEm }];
    d.movs[id] = [mov].concat(prev);
  }
  function recalcPonta(d, ref) {
    if (ESTQ.isMat(ref)) return; // material não tem ponta
    const m2 = ESTQ.loteM2(d, ref);
    ESTQ.lotePallets(d, ref).forEach((p) => {
      const lim = (ESTQ.PRODUTOS[p.prod] || d.produtos[p.prod]).limitePonta || 20;
      if (m2 > 0 && m2 < lim && p.status === 'normal') p.status = 'ponta_sugerida';
      if (m2 >= lim && p.status === 'ponta_sugerida') p.status = 'normal';
    });
  }
  function posLabelId(d, id) {
    const pos = ESTQ.findPos(d, id);
    return pos ? (pos.rascunho ? 'Rascunho do G' + pos.gal : pos.code + ' · N' + pos.nivel) : '—';
  }

  // ---- mapa: mover / rascunho (ação real — RPC + sessão de terminal) ----
  function moverPallet(palletId, destino, motivo, onOk) {
    garantirOperador(['gestor', 'estoquista'], motivo || 'mover pallets', (op, pin) => {
      const params = { p_pin: pin, p_pallet_id: palletId };
      if (destino.type === 'celula') params.p_destino_celula_id = destino.celulaId;
      else params.p_destino_rascunho_galpao = destino.gal;
      ESTQ.chamarRpc('registrar_movimentacao', params)
        .then(refetch)
        .then(() => { if (onOk) onOk(); })
        .catch((e) => showToast('Não foi possível mover: ' + e.message, null, true));
    });
  }
  function dropMove(cell, toCode) {
    const from = findCell(db, gal, cell.code), to = findCell(db, gal, toCode);
    if (!from.stack.length || to.stack.length >= 4) return;
    const id = from.stack[from.stack.length - 1];
    const label = palLabel(id, db);
    moverPallet(id, { type: 'celula', celulaId: to.id }, 'mover pallets', () => {
      setJustMoved({ code: toCode, k: Date.now() });
      showToast(label + ' movido para ' + toCode);
    });
  }
  function dropRascunho(cell) {
    const from = findCell(db, gal, cell.code);
    if (!from.stack.length) return;
    const id = from.stack[from.stack.length - 1];
    const label = palLabel(id, db);
    moverPallet(id, { type: 'rascunho', gal }, 'mover pallets', () => {
      showToast(label + ' enviado para o Rascunho do G' + gal);
    });
  }
  function rejectDrop(code) { setShake({ code, k: Date.now() }); showToast('Posição cheia (4 de 4) — escolha outra', null, true); }

  // ---- fluxo Mover ----
  function startMoverFromCell(palletId) { setSheet(null); setMover({ palletId, source: { type: 'celula', gal, code: sheet, palletId } }); }
  function startMoverFromRasc(palletId) { setMover({ palletId, source: { type: 'rascunho', gal, palletId } }); }
  function escolherDestino(dest) {
    const m = mover; setMover(null);
    if (dest.type === 'rascunho') {
      if (m.source.type === 'rascunho' && m.source.gal === gal) return;
      const label = palLabel(m.palletId, db);
      moverPallet(m.palletId, { type: 'rascunho', gal: m.source.gal }, 'mover pallets', () => {
        showToast(label + ' enviado para o Rascunho do G' + m.source.gal);
      });
    } else { setGal(dest.gal); setView('mapa'); setSubtab('mapa'); setPlacing({ palletId: m.palletId, source: m.source }); }
  }
  function placeAt(cell) {
    if (cell.stack.length >= 4) return;
    if (pickingEntrada) { setEntradaPick({ code: cell.code, id: cell.id, gal }); setPickingEntrada(null); return; }
    const p = placing; if (!p) return;
    const label = palLabel(p.palletId, db);
    moverPallet(p.palletId, { type: 'celula', celulaId: cell.id }, 'mover pallets', () => {
      setPlacing(null); setJustMoved({ code: cell.code, k: Date.now() });
      showToast(label + ' posicionado em ' + cell.code);
    });
  }
  function confirmarPonta(palletId) {
    const ref = db.pallets[palletId];
    garantirOperador(['gestor'], 'confirmar ponta', (op, pin) => {
      ESTQ.chamarRpc('confirmar_ponta', { p_pin: pin, p_pallet_id: palletId })
        .then(refetch)
        .then(() => showToast('Ponta confirmada — lote ' + ref.lote + ' agora vale 50%. Mova para a rua de pontas.'))
        .catch((e) => showToast('Não foi possível confirmar: ' + e.message, null, true));
    });
  }
  function reverterPonta(palletId) {
    const ref = db.pallets[palletId];
    garantirOperador(['gestor'], 'reverter ponta', (op, pin) => {
      ESTQ.chamarRpc('reverter_ponta', { p_pin: pin, p_pallet_id: palletId })
        .then(refetch)
        .then(() => showToast('Ponta desfeita — lote ' + ref.lote + ' volta ao preço normal.'))
        .catch((e) => showToast('Não foi possível desfazer: ' + e.message, null, true));
    });
  }

  // ---- Saída de venda ----
  function startSaida(palletId) {
    setSheet(null);
    setSaida({ palletId, origemLabel: posLabelId(db, palletId) });
  }
  function confirmSaida({ operador, pin, venda, qtd }) {
    const id = saida.palletId; const pal = db.pallets[id]; const pos = ESTQ.findPos(db, id);
    const zeraPrevisto = qtd >= pal.cx;
    // não fecha `saida` aqui — o SaidaWizard mostra o splash de sucesso e se
    // fecha sozinho (onClose) depois; fechar daqui desmontaria o wizard antes
    // dele conseguir mostrar o próprio "sucesso".
    return ESTQ.chamarRpc('registrar_saida_venda', { p_pin: pin, p_pallet_id: id, p_qtd: qtd, p_numero_venda: venda })
      .then(refetch)
      .then(() => {
        if (zeraPrevisto && pos && !pos.rascunho) setJustMoved({ code: pos.code, k: Date.now() });
        showToast('Saída registrada — venda ' + venda + (zeraPrevisto && pos && !pos.rascunho ? ' · posição ' + pos.code + ' liberada' : ''), null, true);
      });
  }

  // ---- Ajuste ----
  function confirmAjuste({ operador, pin, nova, delta, obs, motivoTag }) {
    const id = ajuste.palletId; const pal = db.pallets[id]; const pos = ESTQ.findPos(db, id);
    const zeraPrevisto = nova <= 0;
    return ESTQ.chamarRpc('registrar_ajuste', { p_pin: pin, p_pallet_id: id, p_nova_qtd: nova, p_observacao: obs })
      .then(refetch)
      .then(() => {
        setAjuste(null);
        if (zeraPrevisto && pos && !pos.rascunho) setJustMoved({ code: pos.code, k: Date.now() });
        const unid = ESTQ.isMat(pal, db) ? '' : ' caixas';
        showToast('Ajuste registrado: ' + (delta > 0 ? '+' + delta : delta) + unid + (motivoTag ? ' (' + motivoTag + ')' : ''));
      });
  }

  // ---- Entrada ----
  function requestPickEntrada(info) { setPickingEntrada(info); setView('mapa'); setSubtab('mapa'); }
  function confirmEntrada({ operador, pin, prodId, lote, ton, bitola, validade, qtd, destino }) {
    return ESTQ.chamarRpc('registrar_entrada', {
      p_pin: pin, p_produto_id: prodId, p_lote: lote || null, p_tonalidade: ton || null,
      p_bitola: bitola || null, p_validade: validade || null, p_qtd: qtd, p_galpao_id: destino.gal,
      p_celula_id: destino.type === 'celula' ? destino.celulaId : null,
    })
      .then(refetch)
      .then(() => {
        setEntradaPick(null);
        if (destino.type === 'celula') { setGal(destino.gal); setJustMoved({ code: destino.code, k: Date.now() }); }
      });
  }
  function fecharEntrada() { setEntradaOpen(false); setEntradaPick(null); setPickingEntrada(null); }

  // ---- Cadastros ----
  function salvarProduto(p, pin) {
    return ESTQ.chamarRpc('cadastrar_produto', {
      p_pin: pin, p_id: p.id || null, p_categoria: p.cat, p_nome: p.nome,
      p_codigo: p.codigo || null, p_marca: p.marca || null,
      p_formato: p.cat === 'piso' ? (p.formato || null) : null,
      p_m2_por_caixa: p.cat === 'piso' ? (p.m2cx || null) : null,
      p_limite_ponta_m2: p.cat === 'piso' ? (p.limitePonta || null) : null,
      p_unidade: p.cat === 'material' ? (p.unidade || null) : null,
      p_cor_hex: p.cor || null,
      p_foto_url: p.fotoUrl || null,
    }).then((novoId) => refetch().then(() => novoId));
  }
  function arquivarProduto(id, pin) {
    return ESTQ.chamarRpc('arquivar_produto', { p_pin: pin, p_produto_id: id })
      .then(refetch)
      .then(() => showToast('Produto arquivado — segue no histórico, mas some das listas'))
      .catch((e) => showToast('Não foi possível arquivar: ' + e.message, null, true));
  }
  function salvarOperador(o, pin) {
    return ESTQ.chamarRpc('cadastrar_operador', {
      p_pin: pin, p_id: o.id || null, p_nome: o.nome, p_papel: o.papel,
      p_novo_pin: o.novoPin || null, p_ativo: o.ativo,
    }).then(refetch);
  }

  // ---- detalhe / ver tudo ----
  function irParaPosicao(pos) { setDetail(null); setGal(pos.gal); setView('mapa'); setSubtab('mapa'); setSheet(pos.code); }
  function verTudoMovs(palletId) {
    const pal = db.pallets[palletId];
    TAB_STATE.aba = 'movimentacoes';
    TAB_STATE.mov = Object.assign({}, TAB_STATE.mov, { busca: pal.lote || (ESTQ.PRODUTOS[pal.prod] || db.produtos[pal.prod]).curto, venda: '', tipo: 'todos', operador: 'todos' });
    setDetail(null); setSheet(null); setView('tabela');
  }

  if (loadErro) {
    return (
      <div className="viewport" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
        Erro ao carregar o estoque: {loadErro}
      </div>
    );
  }
  if (!db || gal === null) {
    return (
      <div className="viewport" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
        Carregando estoque…
      </div>
    );
  }

  const rascCount = {};
  galIds.forEach((g) => { rascCount[g] = db.galpoes[g].rascunho.length; });
  const emPosicionamento = !!placing || !!pickingEntrada;
  const placingPal = placing ? db.pallets[placing.palletId] : null;
  const placingProd = placing ? (ESTQ.PRODUTOS[placingPal.prod] || db.produtos[placingPal.prod]) : (pickingEntrada ? (ESTQ.PRODUTOS[pickingEntrada.prodId] || db.produtos[pickingEntrada.prodId]) : null);

  function tabelaSaida(id) { setSaida({ palletId: id, origemLabel: posLabelId(db, id) }); }

  return (
    <div className="viewport">
      <div className="stage" ref={stageEl} style={{ width: dim.w, height: dim.h }} data-view={view}>

        {/* ===== Header ===== */}
        <header className="hdr">
          <div className="brand">
            <img src="estoque/logo-icon.png" alt="" className="brand-logo" />
            <div>
              <div className="name">Panofo</div>
              <div className="sub">Estoque de pisos</div>
            </div>
          </div>

          <nav className="primary-nav" aria-label="Seções">
            <button className={'pn-btn' + (view === 'mapa' ? ' active' : '')} disabled={emPosicionamento} onClick={() => setView('mapa')}>
              <EIcon name="mapPin" size={18} /> Mapa
            </button>
            <button className={'pn-btn' + (view === 'tabela' ? ' active' : '')} disabled={emPosicionamento} onClick={() => setView('tabela')}>
              <EIcon name="table" size={18} /> Tabela
            </button>
          </nav>

          {view === 'mapa' && !emPosicionamento && (
            <nav className="gal-tabs" aria-label="Galpões">
              {galIds.map((g) => (
                <button key={g} className={'gal-tab' + (g === gal ? ' active' : '')} onClick={() => { setGal(g); setSubtab('mapa'); setSheet(null); }}>
                  G{g}{rascCount[g] > 0 && <span className="badge-rasc">{rascCount[g]}</span>}
                </button>
              ))}
            </nav>
          )}

          <div className="hdr-right">
            {sessaoAtiva ? (
              <button className="sessao-badge" title="Trocar operador" onClick={lockSessao}>
                <EIcon name="user" size={15} /> {sessao.operador.nome} <EIcon name="lock" size={13} />
              </button>
            ) : (
              <button className="sessao-badge off" title="Identificar-se"
                onClick={() => garantirOperador(null, 'usar o terminal', () => {})}>
                <EIcon name="user" size={15} /> Entrar
              </button>
            )}
            {podeEntrada && (
              <button className="btn-entrada" disabled={!online} onClick={() => setEntradaOpen(true)}>
                <EIcon name="plus" size={20} strokeWidth={2.2} /> Entrada
              </button>
            )}
            {podeCadastros && (
              <button className={'btn-gear' + (view === 'editorMapa' ? ' active' : '')} title="Editar mapa" onClick={() => setView('editorMapa')}>
                <EIcon name="edit" size={21} />
              </button>
            )}
            {podeCadastros && (
              <button className={'btn-gear' + (view === 'cadastros' ? ' active' : '')} title="Cadastros" onClick={() => setView('cadastros')}>
                <EIcon name="settings" size={21} />
              </button>
            )}
            <div className={'conn' + (online ? '' : ' off')}>
              <span className="dot"></span>{online ? 'Conectado' : 'Sem conexão'}
            </div>
          </div>
        </header>

        {/* ===== Banner offline ===== */}
        {!online && (
          <div className="offline-banner">
            <EIcon name="wifiOff" size={19} /> Modo consulta — sem conexão. Movimentações desabilitadas.
          </div>
        )}

        {/* ===== MAPA ===== */}
        {view === 'mapa' && (
          <React.Fragment>
            {emPosicionamento ? (
              <div className="place-banner">
                <div className="msg">
                  {pickingEntrada ? 'Toque numa posição livre para a entrada' : 'Toque numa posição livre do G' + gal + ' para posicionar'}
                  <span className="pal-chip">
                    <span className="sw" style={{ background: placingProd.cor }}></span>
                    {placingProd.curto}{placing ? ' · ' + ESTQ.qtdCurto(placingPal) : ' · ' + pickingEntrada.qtd + ' un'}
                  </span>
                </div>
                <button className="btn-cancel-place" onClick={() => { setPlacing(null); setPickingEntrada(null); }}>Cancelar</button>
              </div>
            ) : (
              <div className="subbar">
                <div className="subtabs">
                  <button className={'subtab' + (subtab === 'mapa' ? ' active' : '')} onClick={() => setSubtab('mapa')}>Mapa</button>
                  <button className={'subtab' + (subtab === 'rascunho' ? ' active' : '')} onClick={() => setSubtab('rascunho')}>
                    Rascunho <span className="n">{rascCount[gal]}</span>
                  </button>
                </div>
                <span className="gal-name">Galpão {gal}</span>
                <div className="legend">
                  <span className="lg"><span className="sw livre"></span> Livre</span>
                  <span className="lg"><span className="sw sug"></span> Ponta?</span>
                  <span className="lg"><span className="sw pon"></span> Ponta</span>
                  <span className="lg"><span className="sw venc"></span> Vencido</span>
                </div>
              </div>
            )}

            {subtab === 'mapa' ? (
              <MapView
                db={db} gal={gal} canWrite={canMover} placing={emPosicionamento}
                showCodes={t.codigos} justMoved={justMoved} shake={shake} stageEl={stageEl}
                onTapCell={(cell) => { if (cell.stack.length) setSheet(cell.code); }}
                onDropMove={dropMove} onDropRascunho={dropRascunho} onReject={rejectDrop}
                onPlace={placeAt} onOpenRascunho={() => setSubtab('rascunho')}
              />
            ) : (
              <RascunhoView db={db} gal={gal} canWrite={canWrite} papel={papel}
                onPosicionar={startMoverFromRasc} onDetalhe={(id) => setDetail(id)} />
            )}
          </React.Fragment>
        )}

        {/* ===== TABELA ===== */}
        {view === 'tabela' && (
          <TabelaView db={db} papel={papel} online={online}
            onLinha={(id) => setDetail(id)} onSaida={tabelaSaida} onAjuste={(id) => setAjuste({ palletId: id })} />
        )}

        {/* ===== CADASTROS ===== */}
        {view === 'cadastros' && (
          <CadastrosView db={db} online={online} sessaoInicial={sessaoParaGestor} onAutenticado={estabelecerSessao}
            onCriarProduto={salvarProduto} onEditarProduto={salvarProduto} onArquivarProduto={arquivarProduto}
            onSalvarOperador={salvarOperador} onClose={() => setView('mapa')} />
        )}

        {/* ===== EDITOR DE MAPA (gestor) ===== */}
        {view === 'editorMapa' && (
          <EditorMapaView db={db} onClose={() => setView('mapa')} onSaved={refetch}
            sessaoInicial={sessaoParaGestor} onAutenticado={estabelecerSessao} />
        )}

        {/* ===== Sheets / overlays ===== */}
        {sheet && !detail && (
          <PilhaSheet db={db} gal={gal} cellCode={sheet} online={online} papel={papel}
            onClose={() => setSheet(null)} onMover={startMoverFromCell} onSaida={startSaida}
            onConfirmarPonta={(id) => { setSheet(null); confirmarPonta(id); }}
            onReverterPonta={(id) => { setSheet(null); reverterPonta(id); }}
            onAjustar={(id) => { setSheet(null); setAjuste({ palletId: id }); }}
            onDetalhe={(id) => setDetail(id)} />
        )}

        {detail && (
          <DetalheSheet db={db} palletId={detail}
            onVoltar={() => setDetail(null)} onFechar={() => { setDetail(null); setSheet(null); }}
            onIrPara={irParaPosicao} onVerTudo={verTudoMovs} />
        )}

        {mover && (
          <MoverModal db={db} palletId={mover.palletId} origemGal={mover.source.gal}
            onEscolher={escolherDestino} onCancelar={() => setMover(null)} />
        )}

        {saida && (
          <SaidaWizard db={db} palletId={saida.palletId} origemLabel={saida.origemLabel} online={online}
            sessaoInicial={sessaoParaSaida} onAutenticado={estabelecerSessao} onConfirm={confirmSaida} onClose={() => setSaida(null)} />
        )}

        {ajuste && (
          <AjusteSheet db={db} palletId={ajuste.palletId} online={online} sessaoInicial={sessaoParaAjuste}
            onAutenticado={estabelecerSessao} onConfirm={confirmAjuste} onClose={() => setAjuste(null)} />
        )}

        {pinGate && (
          <div className="scrim center" onClick={() => setPinGate(null)}>
            <div className="ajuste-sheet" data-screen-label="Destravar terminal" onClick={(e) => e.stopPropagation()}>
              <div className="np-head">
                <h2>Quem está mexendo?</h2>
                <button className="btn-fechar-sheet" onClick={() => setPinGate(null)}><EIcon name="x" size={19} /> Cancelar</button>
              </div>
              <div className="ajuste-body pin">
                <div className="ajuste-resumo-pin">Digite seu PIN para {pinGate.motivo}<br /><span style={{ fontSize: 12, color: 'var(--gray-500)' }}>o terminal fica destravado por {SESSAO_MS / 60000} min</span></div>
                <PinPad
                  permite={pinGate.papeis ? (op) => pinGate.papeis.includes(op.papel) : undefined}
                  acaoLabel={pinGate.motivo}
                  onValidado={(op, pin) => {
                    estabelecerSessao(op, pin);
                    const onOk = pinGate.onOk; setPinGate(null); onOk(op, pin);
                  }}
                />
              </div>
            </div>
          </div>
        )}

        {entradaOpen && (
          <EntradaWizard db={db} online={online} hidden={!!pickingEntrada} pickedCell={entradaPick} papel={papel}
            sessaoInicial={sessaoParaEntrada} onAutenticado={estabelecerSessao}
            onRequestPick={requestPickEntrada} onConfirm={confirmEntrada} onCriarProduto={salvarProduto} onClose={fecharEntrada} />
        )}

        {/* ===== Toast ===== */}
        {toast && (
          <div className={'undo-toast' + (toast.info ? ' info' : '')} key={toast.k}>
            <span className="msg">{toast.msg}</span>
            {toast.undoDb && (
              <button className="btn-undo" onClick={undo}>
                <EIcon name="undo" size={18} style={{ verticalAlign: '-3px', marginRight: 7 }} /> DESFAZER
              </button>
            )}
            <button className="btn-fechar" onClick={() => { clearTimeout(toastTimer.current); setToast(null); }}><EIcon name="x" size={20} /></button>
            <span className="timebar" style={{ animationDuration: UNDO_MS + 'ms' }}></span>
          </div>
        )}

        {/* ===== Tweaks ===== */}
        <TweaksPanel>
          <TweakSection label="Simulação" />
          <TweakRadio label="Conexão" value={t.conexao} options={['online', 'offline']} onChange={(v) => setTweak('conexao', v)} />
          <TweakSection label="Mapa" />
          <TweakToggle label="Códigos de célula" value={t.codigos} onChange={(v) => setTweak('codigos', v)} />
        </TweaksPanel>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
