// ═══ GENESIS BUILD rbac-fase-a · 2026-07-10 ═══
// Tidal — Cabines: vagas (berths) com gênero, turno, compatibilidade e export PDF.
// Sistema de papel-chave: cada CARD é uma VAGA (não uma cabine inteira).
// Algoritmo de compatibilidade: vaga mostra avatares dos colegas de cabine + sinais
//  - mesmo turno = ✓ rest (não acordam um ao outro)
//  - turno oposto = ⚠ (acorda) ou ✓ hot-bunk (depende da política — aqui a UI alerta)
//  - misto de gênero numa cabine de >1 berço com gênero declarado = ✗ bloqueio

const Cabines = ({state, setState}) => {
  const tt = useT();
  const lang = tt.lang;
  const toast = useToast();
  const isMobile = useMobile();
  const [dragging, setDragging] = useState(null);
  const [dropTarget, setDropTarget] = useState(null);
  const [genderAlert, setGenderAlert] = useState(null);  // {nome, vaga, labels} — modal de confirmação
  const [overUnassign, setOverUnassign] = useState(false);
  const [history, setHistory] = useState([]);
  // ─── Persistência leve dos filtros e preferências de visualização ───
  const loadCabinesUI = () => {
    try { return JSON.parse(localStorage.getItem('genesis_cabines_ui') || '{}'); }
    catch { return {}; }
  };
  const savedUI = loadCabinesUI();
  const [search, setSearch] = useState('');
  const [filterDeck, setFilterDeck] = useState(savedUI.filterDeck || 'all');
  const [filterTurno, setFilterTurno] = useState(savedUI.filterTurno || 'all');
  const [filterEmpresa, setFilterEmpresa] = useState(savedUI.filterEmpresa || 'all');
  const [filterGenero, setFilterGenero] = useState(savedUI.filterGenero || 'all');   // all | M | F
  const [filterStatus, setFilterStatus] = useState(savedUI.filterStatus || 'all');   // all | ocupada | livre
  const [lazyView, setLazyView] = useState(savedUI.lazyView === true);
  const [swapAsk, setSwapAsk] = useState(null);      // {userId, vaga} — troca em 1 passo
  const [histBerth, setHistBerth] = useState(null);  // id da vaga com histórico aberto
  const [resolvendo, setResolvendo] = useState(false);
  const [acoesOpen, setAcoesOpen] = useState(false);
  const [filtroOpen, setFiltroOpen] = useState(false);
  const [hoveredUser, setHoveredUser] = useState(null);
  const [managing, setManaging] = useState(false);
  const [filterRole, setFilterRole] = useState(savedUI.filterRole || 'all');
  const [filterSexo, setFilterSexo] = useState('all');   // all | F | M — ciclo pelo botão ⚤♀♂ do painel
  // ─── Layout centrado no mapa: cabine selecionada, aba do painel lateral, fullscreen ───
  const [selCabin, setSelCabin] = useState(null);        // chave da cabine selecionada no DeckPlan
  const [focusCabin, setFocusCabin] = useState(null);    // {num, ts} — clique num card da aba Plano localiza a cabine no mapa
  const [planoHover, setPlanoHover] = useState(null);    // nº da cabine com hover na aba Plano
  const [panelTab, setPanelTab] = useState('acomodar');  // 'acomodar' | 'plano'
  const [mapFull, setMapFull] = useState(false);
  // ─── Planejamento (simulações de arrumação) ───
  const [mode, setMode] = useState('live');          // 'live' | 'plan'
  const [plans, setPlans] = useState([]);
  const [currentPlanId, setCurrentPlanId] = useState(null);
  const [planName, setPlanName] = useState('');
  const [planAssign, setPlanAssign] = useState({});  // { berthId: userId }
  const [planPool, setPlanPool] = useState(() => new Set());  // userIds importados pro plano ("a acomodar")
  const [importOpen, setImportOpen] = useState(false);
  const [planDirty, setPlanDirty] = useState(false);
  const [savingPlan, setSavingPlan] = useState(false);
  const [applyOpen, setApplyOpen] = useState(false);
  const [modeChoiceOpen, setModeChoiceOpen] = useState(false);
  const [loadOpen, setLoadOpen] = useState(false);
  // micro-autorização: admin/staff (legado) OU a chave cabines.gerir do perfil de permissões
  const isAdmin = state.currentUser?.role === 'admin' || state.currentUser?.is_staff === true || state.role === 'admin'
    || (state.currentUser?.permissoes || []).includes('cabines.gerir');

  // ─── Dados reais da API ───
  const [cabins, setCabins] = useState([]);
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  const token = () => localStorage.getItem('genesis_token');

  const [intents, setIntents] = useState([]);
  const empresas = useMemo(() => [...new Set((users||[]).map(u=>u.empresa).filter(Boolean))].sort(), [users]);

  const fetchData = async () => {
    setLoading(true);
    try {
      const [cabRes, usersRes] = await Promise.all([
        fetch('/api/cabins', { headers: { 'Authorization': `Bearer ${token()}` } }),
        fetch('/api/users',  { headers: { 'Authorization': `Bearer ${token()}` } }),
      ]);
      if (cabRes.ok)   setCabins(await cabRes.json());
      if (usersRes.ok) setUsers(await usersRes.json());
      // intenções do lazy embark (pro flip dos cards na aba ao vivo)
      if (isAdmin) { try { const ir = await fetch('/api/cabins/plans/intents', { headers: { 'Authorization': `Bearer ${token()}` } }); if (ir.ok) setIntents(await ir.json()); } catch(e){} }
    } catch(e) { console.error(e); }
    finally { setLoading(false); }
  };

  useEffect(() => { fetchData(); }, []);

  // ─── Salva filtros e preferências de visualização no aparelho ───
  useEffect(() => {
    try {
      localStorage.setItem('genesis_cabines_ui', JSON.stringify({
        filterDeck, filterTurno, filterEmpresa, filterGenero, filterStatus, filterRole, lazyView
      }));
    } catch {}
  }, [filterDeck, filterTurno, filterEmpresa, filterGenero, filterStatus, filterRole, lazyView]);

  // ─── Atalhos de teclado no módulo Cabines ───
  useEffect(() => {
    const onKey = (e) => {
      const inInput = e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable);
      // Esc limpa o drag ou fecha o filtro
      if (e.key === 'Escape') {
        if (dragging) { e.preventDefault(); setDragging(null); setDropTarget(null); return; }
        if (filtroOpen) { e.preventDefault(); setFiltroOpen(false); return; }
        if (mapFull) { e.preventDefault(); setMapFull(false); return; }
        if (selCabin) { e.preventDefault(); setSelCabin(null); return; }
      }
      // Ctrl/Cmd + Z desfaz a última atribuição, exceto em campos de texto
      if (!inInput && (e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z' && !e.shiftKey) {
        e.preventDefault();
        if (history.length) undoLast();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [dragging, filtroOpen, history, mapFull, selCabin]);

  // Mapa do estado AO VIVO (berthId -> userId), independente do modo
  const liveAssign = useMemo(() => {
    const m = {};
    cabins.forEach(c => (c.berths_detail||[]).forEach(b => { if (b.user_id) m[b.berth_id] = parseInt(b.user_id); }));
    return m;
  }, [cabins]);

  // Adapta o formato da API para o formato esperado pelo componente.
  // Em modo "plan", a ocupação de cada vaga vem do rascunho (planAssign).
  const adaptedCabins = useMemo(() => cabins.map(c => ({
    num: c.number,
    deck: parseInt(c.deck),
    tipo: c.type,
    berths: c.berths_detail.map(b => ({
      id: b.berth_id,
      user: mode==='plan'
        ? (planAssign[b.berth_id] ?? null)
        : (b.user_id ? parseInt(b.user_id) : null),
    }))
  })), [cabins, mode, planAssign]);

  const usersById = useMemo(() => {
    const map = {};
    users.forEach(u => {
      const entry = {
        ...u,
        status: u.status_bordo,
        cab: u.cabine_atual,
        genero: u.genero,
        turno: u.turno,
      };
      map[u.id] = entry;
      map[String(u.id)] = entry; // garante lookup por string ou número
    });
    return map;
  }, [users]);

  const cabinByBerth = useMemo(() => {
    const map = {};
    adaptedCabins.forEach(c => c.berths.forEach(b => map[b.id] = c));
    return map;
  }, [adaptedCabins]);

  const placedInPlan = useMemo(() => new Set(Object.values(planAssign)), [planAssign]);
  const unassigned = mode==='plan'
    ? users.filter(u => planPool.has(u.id) && !placedInPlan.has(u.id))
    : users.filter(u => u.status_bordo === 'a_bordo' && !u.cabine_atual && u.role !== 'guest' && u.role !== 'admin');

  // Build flat list of all vagas (berths) with deck/cabin context
  const vagas = useMemo(() => {
    const arr = [];
    adaptedCabins.forEach(cab => {
      cab.berths.forEach(b => {
        arr.push({
          ...b,
          deck: cab.deck,
          cabinNum: cab.num,
          cabinType: cab.tipo,
          cabinBerths: cab.berths,
        });
      });
    });
    return arr;
  }, [adaptedCabins]);

  // Vagas agrupadas por deck (para o relatório PDF — sempre o barco inteiro, sem filtros)
  const decks = useMemo(() => {
    const grouped = {};
    vagas.forEach(v => {
      const k = String(v.deck);
      (grouped[k] = grouped[k] || []).push(v);
    });
    return Object.keys(grouped).sort((a,b)=>b-a).map(d => ({deck:d, vagas:grouped[d]}));
  }, [vagas]);

  // Stats
  const totalBerths = vagas.length;
  const occupied = vagas.filter(v => v.user).length;
  const femaleVagas = vagas.filter(v => v.user && usersById[v.user]?.genero === 'F').length;

  // Stats por CABINE (livre/parcial/completa) — alimenta o strip de KPIs
  const cabinStats = useMemo(() => {
    let livres = 0, parciais = 0, completas = 0;
    adaptedCabins.forEach(c => {
      const tot = c.berths.length;
      const occ = c.berths.filter(b => b.user).length;
      if (!tot) return;
      if (occ === 0) livres++;
      else if (occ >= tot) completas++;
      else parciais++;
    });
    return { livres, parciais, completas, total: livres + parciais + completas };
  }, [adaptedCabins]);

  // Cabines cujos ocupantes casam com a busca/filtro de sexo → highlight âmbar no mapa
  const highlightCabs = useMemo(() => {
    if (!search && filterSexo==='all') return null;
    const q = search.toLowerCase();
    const s = new Set();
    vagas.forEach(v => {
      const u = v.user ? usersById[v.user] : null;
      if (!u) return;
      if (filterSexo!=='all' && u.genero!==filterSexo) return;
      if (q && !(u.nome.toLowerCase().includes(q) || String(u.genesis_id||'').toLowerCase().includes(q) || (u.empresa||'').toLowerCase().includes(q))) return;
      s.add(String(v.cabinNum));
    });
    return s;
  }, [search, filterSexo, vagas, usersById]);

  // Aba Plano: cabines agrupadas por deck, filtradas pela busca/sexo (casa cabine ou ocupante)
  const planoPorDeck = useMemo(() => {
    const q = search.toLowerCase();
    const filtrando = !!q || filterSexo!=='all';
    const matchUser = (u) => u && (filterSexo==='all' || u.genero===filterSexo) &&
      (!q || u.nome.toLowerCase().includes(q) || String(u.genesis_id||'').toLowerCase().includes(q) || (u.empresa||'').toLowerCase().includes(q));
    const byDeck = {};
    adaptedCabins.forEach(c => {
      const cabMatch = !!q && String(c.num).toLowerCase().includes(q);
      const berths = c.berths.map(b => ({ ...b, u: b.user ? usersById[b.user] : null }));
      if (filtrando && !cabMatch && !berths.some(b => matchUser(b.u))) return;
      const berthsView = filtrando && !cabMatch ? berths.filter(b => !b.u || matchUser(b.u)) : berths;
      (byDeck[c.deck] = byDeck[c.deck] || []).push({ ...c, berthsView });
    });
    return byDeck;
  }, [adaptedCabins, usersById, search, filterSexo]);

  // ─── Compatibility check for a target berth ───
  // Em camarotes de 4 (ex.: 704 A1/A2/B1/B2) cada letra é um quarto separado.
  // roomOf extrai o quarto a partir do id da vaga ("704-A1"->"A", "604-1"->"*", "913"->"*").
  const roomOf = (berthId) => {
    const slot = String(berthId).split('-').slice(1).join('-');
    const m = slot.match(/^([A-Za-z])/);
    return m ? m[1].toUpperCase() : '*';
  };

  // Conflitos do PLANO: camarotes/quartos com gênero misto (aviso suave, não bloqueia)
  const planConflitos = useMemo(() => {
    if (mode !== 'plan') return [];
    const quartos = {};
    Object.entries(planAssign).forEach(([berthId, userId]) => {
      const cabNum = String(berthId).split('-')[0];
      const room = roomOf(berthId);
      const key = `${cabNum}|${room}`;
      (quartos[key] = quartos[key] || { cabNum, room, ocupantes: [] }).ocupantes.push(userId);
    });
    const conflitos = [];
    Object.values(quartos).forEach(q => {
      if (q.ocupantes.length < 2) return;
      const pessoas = q.ocupantes.map(id => usersById[id]).filter(Boolean);
      const generos = [...new Set(pessoas.map(u => u.genero).filter(Boolean))];
      if (generos.length > 1) conflitos.push({ cabNum: q.cabNum, room: q.room, nomes: pessoas.map(u => ({ nome: u.nome, genero: u.genero })) });
    });
    return conflitos;
  }, [mode, planAssign, usersById]);


  const compatCheck = (userId, berthId) => {
    if (!userId || !berthId) return {ok:true, signals:[]};
    const u = usersById[userId];
    const cab = cabinByBerth[berthId];
    if (!u || !cab) return {ok:true, signals:[]};
    // Só considera colegas do MESMO QUARTO (A1↔A2, B1↔B2 — não cruza A com B)
    const tRoom = roomOf(berthId);
    const others = cab.berths
      .filter(b => b.id !== berthId && b.user && roomOf(b.id) === tRoom)
      .map(b => usersById[b.user]).filter(Boolean);
    const signals = [];

    if (others.length) {
      // Gênero diferente no quarto → AVISO (não bloqueia mais)
      const otherGenders = [...new Set(others.map(o => o.genero).filter(Boolean))];
      if (u.genero && otherGenders.length && !otherGenders.includes(u.genero)) {
        signals.push({type:'warn', icon:'alert', label: lang==='pt'?'gênero diferente no quarto':'different gender in room'});
      }
      // Turno: mesmo turno → AVISO (dormem no mesmo horário); turnos opostos → ideal
      const sameShift = others.filter(o => o.turno && o.turno === u.turno).length;
      const oppShift  = others.filter(o => o.turno && u.turno && o.turno !== u.turno).length;
      if (sameShift) signals.push({type:'warn', icon:'bell', label: lang==='pt'?`${sameShift} colega no mesmo turno`:`${sameShift} same-shift`});
      if (oppShift)  signals.push({type:'good', icon:'check', label: lang==='pt'?`${oppShift} em turno oposto`:`${oppShift} opposite-shift`});
    }

    // Nunca bloqueia — apenas sinaliza
    return {ok:true, signals, others};
  };

  const removeFromCabin = async (userId) => {
    try {
      const res = await fetch(`/api/cabins/assign/${userId}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${token()}` }
      });
      if (res.ok) {
        await fetchData();
        toast.push({icon:'check', title: lang==='pt'?'Removido da cabine':'Removed from cabin'});
      } else {
        const d = await res.json();
        toast.push({icon:'x', title: d.error || 'Erro ao remover'});
      }
    } catch(e) { toast.push({icon:'x', title:'Erro de conexão'}); }
  };

  const assign = async (userId, berthId) => {
    const check = compatCheck(userId, berthId);
    try {
      const res = await fetch('/api/cabins/assign', {
        method: 'POST',
        headers: { 'Content-Type':'application/json', 'Authorization': `Bearer ${token()}` },
        body: JSON.stringify({ user_id: userId, berth_id: berthId })
      });
      const data = await res.json();
      if (res.ok) {
        setHistory(h => [...h, {userId, berthId}]);
        await fetchData();
        const u = usersById[userId];
        const warns = check.signals.filter(s => s.type === 'warn');
        const genderWarn = check.signals.find(s => s.icon === 'alert');
        if (genderWarn) {
          setGenderAlert({ nome: u?.nome || '', vaga: berthId, labels: warns.map(s=>s.label) });
        } else {
          toast.push({
            icon: warns.length ? 'bell' : 'check',
            title: warns.length
              ? (lang==='pt'?`⚠ ${u?.nome} na vaga ${berthId} — confira`:`⚠ ${u?.nome} in ${berthId} — review`)
              : `${u?.nome} ${lang==='pt'?'na vaga':'in berth'} ${berthId}`,
            body: check.signals.length ? check.signals.map(s=>s.label).join(' · ') : (lang==='pt'?'compatibilidade ok':'compat ok'),
            duration: warns.length ? 8000 : 6000,
          });
        }
      } else {
        toast.push({icon:'x', title: data.error || 'Erro ao atribuir vaga'});
      }
    } catch(e) {
      toast.push({icon:'x', title:'Erro de conexão'});
    } finally {
      setDragging(null); setDropTarget(null);
    }
  };

  const undo = async (userId, berthId) => {
    try {
      await fetch(`/api/cabins/assign/${userId}`, {
        method: 'DELETE',
        headers: { 'Authorization': `Bearer ${token()}` }
      });
      await fetchData();
    } catch(e) { console.error(e); }
  };

  const undoLast = () => {
    if (!history.length) return;
    const last = history[history.length-1];
    undo(last.userId, last.berthId);
    setHistory(h => h.slice(0,-1));
    toast.push({icon:'check', title: lang==='pt'?'Desfeito':'Undone', duration:2200});
  };

  // ─────────── Planejamento ───────────
  const fetchPlans = async () => {
    try {
      const r = await fetch('/api/cabins/plans', { headers:{ Authorization:`Bearer ${token()}` } });
      if (r.ok) setPlans(await r.json());
    } catch(e) { console.error(e); }
  };
  useEffect(() => { if (isAdmin) fetchPlans(); }, [isAdmin]);

  const planSet = (userId, berthId) => {
    const check = compatCheck(userId, berthId);
    setPlanAssign(prev => {
      const next = {};
      for (const [bid, uid] of Object.entries(prev)) {
        if (uid === userId) continue;            // tira a pessoa da vaga antiga
        if (bid === String(berthId)) continue;   // libera a vaga de destino
        next[bid] = uid;
      }
      next[berthId] = userId;
      return next;
    });
    setPlanDirty(true); setDragging(null); setDropTarget(null);
    const warns = check.signals.filter(s => s.type === 'warn');
    const genderWarn = check.signals.find(s => s.icon === 'alert');
    const u = usersById[userId];
    if (genderWarn) {
      setGenderAlert({ nome: u?.nome || '', vaga: berthId, labels: warns.map(s=>s.label) });
    } else if (warns.length) {
      toast.push({icon:'bell', title: lang==='pt'?`⚠ ${u?.nome}: ${warns.map(s=>s.label).join(' · ')}`:`⚠ ${u?.nome}: ${warns.map(s=>s.label).join(' · ')}`, duration:7000});
    }
  };
  const planUnset = (userId) => {
    setPlanAssign(prev => {
      const next = {};
      for (const [bid, uid] of Object.entries(prev)) if (uid !== userId) next[bid] = uid;
      return next;
    });
    setPlanDirty(true); setDragging(null); setDropTarget(null);
  };

  // Ações de drag respeitam o modo
  const resolverAgora = async () => {
    setResolvendo(true);
    try {
      const r = await fetch('/api/cabins/plans/resolve', { method:'POST', headers:{ Authorization:`Bearer ${token()}` } });
      const d = await r.json();
      if (r.ok) {
        if (d.resolvidas.length) toast.push({ icon:'check', title: lang==='pt' ? `${d.resolvidas.length} troca(s) resolvida(s)` : `${d.resolvidas.length} move(s) resolved`, body: d.resolvidas.map(x=>`${x.nome} → ${x.berth_id}`).join(' · '), duration: 8000 });
        else toast.push({ icon:'bell', title: lang==='pt' ? 'Nada pronto ainda' : 'Nothing ready yet', body: lang==='pt' ? 'as condições das pendências ainda não bateram' : 'pending conditions not met yet' });
        await fetchData();
      } else toast.push({ icon:'x', title: d.error || 'Erro' });
    } catch(e) { toast.push({ icon:'x', title:'Erro de conexão' }); }
    setResolvendo(false);
  };
  const desarmarLazy = async () => {
    try {
      const r = await fetch('/api/cabins/plans/disarm', { method:'POST', headers:{ Authorization:`Bearer ${token()}` } });
      if (r.ok) { toast.push({ icon:'check', title: lang==='pt'?'Embarque reativo desarmado':'Reactive embark disarmed' }); await fetchData(); }
    } catch(e) { toast.push({ icon:'x', title:'Erro de conexão' }); }
  };
  const berthDe = (userId) => Object.keys(liveAssign).find(b => String(liveAssign[b]) === String(userId)) || null;
  const swapLive = async (userId, vaga, trocar) => {
    const occId = vaga.user;
    const antiga = berthDe(userId);
    try {
      await fetch(`/api/cabins/assign/${occId}`, { method:'DELETE', headers:{ Authorization:`Bearer ${token()}` } });
      const r1 = await fetch('/api/cabins/assign', { method:'POST', headers:{ 'Content-Type':'application/json', Authorization:`Bearer ${token()}` }, body: JSON.stringify({ user_id: userId, berth_id: vaga.id }) });
      if (!r1.ok) throw new Error('assign');
      if (trocar && antiga) await fetch('/api/cabins/assign', { method:'POST', headers:{ 'Content-Type':'application/json', Authorization:`Bearer ${token()}` }, body: JSON.stringify({ user_id: occId, berth_id: antiga }) });
      const uIn = usersById[userId], uOut = usersById[occId];
      toast.push({ icon:'check', title: trocar && antiga
        ? (lang==='pt' ? `🔀 ${uIn?.nome} ↔ ${uOut?.nome}` : `🔀 ${uIn?.nome} ↔ ${uOut?.nome}`)
        : (lang==='pt' ? `${uIn?.nome} na ${vaga.id} · ${uOut?.nome} sem vaga` : `${uIn?.nome} in ${vaga.id} · ${uOut?.nome} unassigned`) });
      await fetchData();
    } catch(e) { toast.push({ icon:'x', title: lang==='pt'?'Não consegui completar a troca':'Swap failed' }); await fetchData(); }
    setDragging(null); setDropTarget(null);
  };
  const swapPlan = (userId, vaga, trocar) => {
    const occId = vaga.user;
    setPlanAssign(prev => {
      const antiga = Object.keys(prev).find(b => String(prev[b]) === String(userId)) || null;
      const next = { ...prev };
      if (antiga) delete next[antiga];
      delete next[vaga.id];
      next[vaga.id] = userId;
      if (trocar && antiga) next[antiga] = occId; // sem antiga → occ volta pro grupo do plano
      return next;
    });
    setPlanDirty(true);
    setDragging(null); setDropTarget(null);
  };
  const doAssign = (userId, berthId) => mode==='plan' ? planSet(userId, berthId) : assign(userId, berthId);
  const doRemove = (userId)          => mode==='plan' ? planUnset(userId)       : removeFromCabin(userId);

  // ─── Handlers do mapa (DeckPlan) ───
  // Clique/drop numa vaga da janelinha: livre → atribui; ocupada → propõe troca (admin)
  const mapaVagaAction = (vg) => {
    if (!dragging || !vg) return;
    if (!vg.user) { doAssign(dragging, vg.id); setDragging(null); }
    else if (String(vg.user) !== String(dragging) && isAdmin) setSwapAsk({ userId: dragging, vaga: vg });
  };
  // Drop no retângulo da cabine: cai na primeira vaga livre; cheia → abre a janelinha
  const mapaCabineDrop = (vagaIds) => {
    if (!dragging) return;
    const livre = (vagaIds || []).map(id => vagas.find(v => v.id === id)).find(v => v && !v.user);
    if (livre) { doAssign(dragging, livre.id); setDragging(null); }
  };
  const mapaUnassign = (userId) => { doRemove(userId); };

  const enterPlan = () => {
    setMode('plan'); // o plano começa em branco; importe pessoas ou o estado atual
  };
  const importarEstadoAtual = () => {
    setPlanAssign({...liveAssign});
    setPlanPool(new Set(Object.values(liveAssign)));
    setPlanDirty(true);
    toast.push({icon:'check', title: lang==='pt'?'Estado atual do barco importado':'Current state imported'});
  };
  const importarPessoas = (ids) => {
    setPlanPool(prev => { const s = new Set(prev); ids.forEach(id => s.add(id)); return s; });
    setImportOpen(false); setPlanDirty(true);
    toast.push({icon:'check', title: lang==='pt'?`${ids.length} pessoa(s) importada(s)`:`${ids.length} person(s) imported`});
  };
  const removerDoPlano = (id) => {
    setPlanPool(prev => { const s = new Set(prev); s.delete(id); return s; });
    planUnset(id); setPlanDirty(true);
  };
  const loadPlan = async (id) => {
    if (!id) { setPlanAssign({}); setPlanPool(new Set()); setCurrentPlanId(null); setPlanName(''); setPlanDirty(false); return; }
    try {
      const r = await fetch(`/api/cabins/plans/${id}`, { headers:{ Authorization:`Bearer ${token()}` } });
      if (!r.ok) { toast.push({icon:'x', title:'Erro ao abrir plano'}); return; }
      const p = await r.json();
      const map = {}; const pool = new Set();
      (p.dados||[]).forEach(it => { if (it.user_id != null) { pool.add(parseInt(it.user_id)); if (it.berth_id) map[it.berth_id] = parseInt(it.user_id); } });
      setPlanAssign(map); setPlanPool(pool); setCurrentPlanId(p.id); setPlanName(p.nome); setPlanDirty(false);
      toast.push({icon:'check', title: `${lang==='pt'?'Plano aberto':'Plan opened'}: ${p.nome}`});
    } catch(e) { toast.push({icon:'x', title:'Erro de conexão'}); }
  };
  const newPlan = () => {
    setPlanAssign({}); setPlanPool(new Set()); setCurrentPlanId(null); setPlanName(''); setPlanDirty(false);
    toast.push({icon:'check', title: lang==='pt'?'Novo plano em branco':'New blank plan', duration:2600});
  };
  const armarLazy = async () => {
    const items = Object.entries(planAssign).map(([berth_id, user_id]) => ({ berth_id, user_id }));
    if (!items.length) { toast.push({icon:'x', title: lang==='pt'?'O plano está vazio':'The plan is empty'}); return; }
    try {
      const r = await fetch('/api/cabins/plans/arm', { method:'POST', headers:{ 'Content-Type':'application/json', Authorization:`Bearer ${token()}` }, body: JSON.stringify({ plano_id: currentPlanId, plano_nome: planName || null, items }) });
      const d = await r.json();
      if (r.ok) {
        toast.push({icon:'check', title: lang==='pt'?'Embarque reativo armado':'Reactive embark armed', body: lang==='pt'?`${d.resolvidas} já resolvida(s) · ${d.pendentes} aguardando`:`${d.resolvidas} resolved · ${d.pendentes} waiting`});
        setModeChoiceOpen(false); setMode('live'); fetchData();
      } else toast.push({icon:'x', title: d.error || (lang==='pt'?'Erro ao armar':'Error arming')});
    } catch(e) { toast.push({icon:'x', title: lang==='pt'?'Erro de conexão':'Connection error'}); }
  };

  const savePlan = async () => {
    if (!planName.trim()) { toast.push({icon:'x', title: lang==='pt'?'Dê um nome ao plano':'Name the plan first'}); return; }
    setSavingPlan(true);
    const dados = [
      ...Object.entries(planAssign).map(([berth_id, user_id]) => ({ berth_id, user_id })),
      ...[...planPool].filter(id => !placedInPlan.has(id)).map(user_id => ({ berth_id: null, user_id }))
    ];
    try {
      const url = currentPlanId ? `/api/cabins/plans/${currentPlanId}` : '/api/cabins/plans';
      const r = await fetch(url, { method: currentPlanId ? 'PUT' : 'POST',
        headers:{ 'Content-Type':'application/json', Authorization:`Bearer ${token()}` },
        body: JSON.stringify({ nome: planName.trim(), dados }) });
      if (r.status === 404) { toast.push({icon:'x', title: lang==='pt'?'Rota de planos não encontrada (404)':'Plans route not found (404)', body: lang==='pt'?'reinicie o container com o other.js novo':'restart the container with the new other.js'}); return; }
      let p = {};
      try { p = await r.json(); } catch(_) { toast.push({icon:'x', title: lang==='pt'?'Resposta inválida do servidor':'Invalid server response', body: lang==='pt'?'o backend pode não ter sido reiniciado':'backend may not be restarted'}); return; }
      if (r.ok) { setCurrentPlanId(p.id); setPlanDirty(false); fetchPlans(); toast.push({icon:'check', title: lang==='pt'?'Plano salvo':'Plan saved'}); }
      else toast.push({icon:'x', title: p.error || (lang==='pt'?'Erro ao salvar':'Error saving')});
    } catch(e) { toast.push({icon:'x', title: lang==='pt'?'Sem resposta do servidor':'No server response', body: lang==='pt'?'backend offline ou não reiniciado':'backend offline or not restarted'}); }
    finally { setSavingPlan(false); }
  };
  const deletePlan = async () => {
    if (!currentPlanId) { setPlanAssign({}); setPlanPool(new Set()); setPlanName(''); setPlanDirty(false); return; }
    if (!window.confirm(lang==='pt'?'Excluir este plano de cabines?':'Delete this cabin plan?')) return;
    try {
      await fetch(`/api/cabins/plans/${currentPlanId}`, { method:'DELETE', headers:{ Authorization:`Bearer ${token()}` } });
      setPlanAssign({}); setPlanPool(new Set()); setCurrentPlanId(null); setPlanName(''); setPlanDirty(false); fetchPlans();
      toast.push({icon:'check', title: lang==='pt'?'Plano excluído':'Plan deleted'});
    } catch(e) { toast.push({icon:'x', title:'Erro de conexão'}); }
  };
  const deletePlanById = async (id) => {
    if (!window.confirm(lang==='pt'?'Excluir este plano?':'Delete this plan?')) return;
    try {
      await fetch(`/api/cabins/plans/${id}`, { method:'DELETE', headers:{ Authorization:`Bearer ${token()}` } });
      if (id===currentPlanId) { setCurrentPlanId(null); setPlanName(''); }
      fetchPlans();
      toast.push({icon:'check', title: lang==='pt'?'Plano excluído':'Plan deleted'});
    } catch(e) { toast.push({icon:'x', title:'Erro de conexão'}); }
  };

  // ─── PDF export of berth manifest ───
  const exportPDF = () => {
    const w = window.open('', '_blank', 'width=900,height=1100');
    const today = new Date().toISOString().split('T')[0];
    const sections = [...decks].sort((a,b)=>+b.deck-+a.deck).map(d => {
      const rows = d.vagas.map(v => {
        const u = v.user ? usersById[v.user] : null;
        const turno = u?.turno ? TURNOS[u.turno]?.label : '—';
        return `<tr>
          <td><strong>${v.id}</strong></td>
          <td>${v.cabinType}</td>
          <td>${u ? u.nome : '<em style="color:#948B7E">vaga livre</em>'}</td>
          <td>${u?.empresa||''}</td>
          <td>${u?.genero==='F' ? '♀' : u?.genero==='M' ? '♂' : ''}</td>
          <td>${turno}</td>
        </tr>`;
      }).join('');
      return `<h2 style="font-family:Fraunces,Georgia,serif;font-size:22px;font-weight:400;margin-top:28px;margin-bottom:8px;letter-spacing:-0.4px">Deck <em>${d.deck}</em></h2>
      <table>
        <thead><tr><th>Vaga</th><th>Tipo</th><th>Ocupante</th><th>Empresa</th><th>G</th><th>Turno</th></tr></thead>
        <tbody>${rows}</tbody>
      </table>`;
    }).join('');
    w.document.write(`<!doctype html><html><head><meta charset="utf-8"><title>Mapa de vagas · ${today}</title>
<style>
  @page{margin:18mm}
  body{font-family:'Inter',Arial,sans-serif;color:#1F2A2E;font-size:11px}
  h1{font-family:'Fraunces',Georgia,serif;font-weight:400;font-size:36px;letter-spacing:-0.7px;margin:0 0 4px}
  h1 em{color:#D9694B;font-style:italic}
  .meta{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:1.5px;text-transform:uppercase;color:#7A8388;margin-bottom:18px}
  .stats{display:flex;gap:24px;margin:18px 0;padding:12px 18px;background:#F5EFE3;border-radius:10px;font-size:11px}
  .stats b{font-family:'Fraunces',Georgia,serif;font-size:20px;font-weight:500;display:block}
  table{width:100%;border-collapse:collapse;font-size:11px;margin-top:6px}
  th{text-align:left;font-family:'IBM Plex Mono',monospace;font-size:9px;letter-spacing:1.2px;text-transform:uppercase;color:#7A8388;padding:8px;border-bottom:1px solid #1F2A2E}
  td{padding:7px 8px;border-bottom:1px solid #EAE3D6}
  .footer{margin-top:36px;font-family:'IBM Plex Mono',monospace;font-size:9px;color:#7A8388;letter-spacing:1px;text-transform:uppercase}
</style></head><body>
<h1>Mapa de <em>vagas</em></h1>
<div class="meta">${today} · Genesis · documento operacional</div>
<div class="stats">
  <div><b>${occupied}/${totalBerths}</b>vagas ocupadas</div>
  <div><b>${totalBerths-occupied}</b>vagas livres</div>
  <div><b>${femaleVagas}</b>femininas</div>
  <div><b>${unassigned.length}</b>aguardando acomodação</div>
</div>
${sections}
<div class="footer">documento sensível · uso interno · operação Genesis</div>
</body></html>`);
    w.document.close();
    setTimeout(()=>w.print(), 300);
  };

  // Available decks for filter
  const allDecks = useMemo(() => [...new Set(vagas.map(v=>String(v.deck)))].sort((a,b)=>+b-+a), [vagas]);

  // Chips compactos de KPI — renderizados dentro do mapa, na linha dos decks
  const kpiChips = (
    <>
      {[
        {n:cabinStats.livres,    l:lang==='pt'?'livres':'free',       c:'#7BB67E'},
        {n:cabinStats.parciais,  l:lang==='pt'?'parciais':'partial',  c:'#E8B453'},
        {n:cabinStats.completas, l:lang==='pt'?'completas':'full',    c:'#C0392B'},
        ...(mode==='plan' && planConflitos.length > 0 ? [{n:planConflitos.length, l:lang==='pt'?'conflitos':'conflicts', c:'var(--accent-2)'}] : [])
      ].map((s,i) => (
        <span key={i} style={{display:'inline-flex',alignItems:'center',gap:5,padding:'4px 9px',borderRadius:20,background:'rgba(245,239,227,0.07)',border:'1px solid rgba(245,239,227,0.14)',fontFamily:'var(--fm)',fontSize:10.5,color:'#F5EFE3',whiteSpace:'nowrap'}}>
          <span style={{width:7,height:7,borderRadius:'50%',background:s.c,flexShrink:0}}/>
          <b style={{fontWeight:600}}>{s.n}</b>
          <span style={{color:'rgba(245,239,227,0.55)'}}>{s.l}</span>
        </span>
      ))}
    </>
  );

  return (
    <main style={{padding: isMobile ? '16px 16px 240px' : '30px 36px 280px', flex:1}}>
      {/* Cabeçalho de página */}
      <div style={{marginBottom:22}}>
        <div className="t-eyebrow">{lang==='pt'?'acomodação a bordo':'accommodation on board'}</div>
        <h1 style={{fontFamily:'var(--fs)',fontSize:isMobile?30:38,letterSpacing:'-0.8px',lineHeight:1.05,marginTop:6}}>
          {lang==='pt'?<>Cab<em style={{fontStyle:'italic',color:'var(--accent)'}}>ines.</em></>:<>Cab<em style={{fontStyle:'italic',color:'var(--accent)'}}>ins.</em></>}
        </h1>
      </div>

      {/* Toggle Ao vivo / Planejamento */}
      <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:16,flexWrap:'wrap'}}>
        <div style={{display:'inline-flex',background:'var(--cream-2)',borderRadius:12,padding:4,gap:4}}>
          {[{id:'live',pt:'Ao vivo',en:'Live'},{id:'plan',pt:'Planejamento',en:'Planning'}].map(t=>(
            <button key={t.id}
              onClick={()=> t.id==='plan' ? enterPlan() : setMode('live')}
              style={{padding:'8px 16px',borderRadius:9,border:'none',cursor:'pointer',fontFamily:'var(--fm)',fontSize:12.5,letterSpacing:0.3,
                background: mode===t.id ? 'var(--surface)' : 'transparent',
                color: mode===t.id ? 'var(--ink)' : 'var(--muted)',
                boxShadow: mode===t.id ? 'var(--shadow-sm)' : 'none', transition:'all .15s'}}>
              {lang==='pt'?t.pt:t.en}
            </button>
          ))}
        </div>
        {mode==='plan' && (
          <span style={{fontFamily:'var(--fm)',fontSize:11,color:'var(--accent-2)',background:'rgba(217,105,75,0.08)',border:'1px solid var(--accent)',borderRadius:20,padding:'4px 12px'}}>
            🗺️ {lang==='pt'?'PLANO — não afeta o estado real do barco':'PLAN — does not affect the live state'}
          </span>
        )}
        <div style={{marginLeft:'auto',display:'flex',alignItems:'center',gap:8}}>
          {mode==='live' && (
            <button className={'btn-icone' + (lazyView ? '' : ' neutro')} onClick={()=>setLazyView(v=>!v)} title={lang==='pt'?'programados pelo embarque reativo':'scheduled by the reactive embark'}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke={lazyView?'#fff':'var(--ink)'} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></svg>
            </button>
          )}
          <div style={{position:'relative'}}>
            <button className="btn-icone" onClick={()=>setAcoesOpen(v=>!v)} title={lang==='pt'?'ações (exportar, gerenciar)':'actions (export, manage)'} style={{fontSize:18,letterSpacing:1}}>⋯</button>
            {acoesOpen && (
              <div style={{position:'absolute',top:'100%',right:0,marginTop:4,zIndex:50,background:'var(--surface)',border:'1px solid var(--line)',borderRadius:10,boxShadow:'var(--shadow-md)',overflow:'hidden',minWidth:190}}>
                <div onClick={()=>{setAcoesOpen(false);exportPDF();}} style={{padding:'11px 14px',fontFamily:'var(--fm)',fontSize:12.5,cursor:'pointer',color:'var(--ink)'}} onMouseEnter={e=>e.currentTarget.style.background='var(--cream-2)'} onMouseLeave={e=>e.currentTarget.style.background='transparent'}>📄 {lang==='pt'?'Exportar PDF':'Export PDF'}</div>
                {isAdmin && <div onClick={()=>{setAcoesOpen(false);setManaging(true);}} style={{padding:'11px 14px',fontFamily:'var(--fm)',fontSize:12.5,cursor:'pointer',color:'var(--ink)',borderTop:'1px solid var(--line-soft)'}} onMouseEnter={e=>e.currentTarget.style.background='var(--cream-2)'} onMouseLeave={e=>e.currentTarget.style.background='transparent'}>⚙ {lang==='pt'?'Gerenciar cabines':'Manage cabins'}</div>}
              </div>
            )}
          </div>
        </div>
      </div>

      {mode==='live' && isAdmin && (
        <LazyBanner intents={intents} lang={lang} lazyView={lazyView} resolvendo={resolvendo}
          onResolver={resolverAgora} onDesarmar={desarmarLazy} onVerCards={()=>setLazyView(v=>!v)}/>
      )}

      {/* Barra de gestão de planos */}
      {mode==='plan' && (
        <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:18,flexWrap:'wrap',padding:'12px 16px',background:'var(--surface)',border:'1px solid var(--line-soft)',borderLeft:'3px solid var(--accent)',borderRadius:14,boxShadow:'var(--shadow-sm)'}}>
          <input className="input" placeholder={lang==='pt'?'nome do plano…':'plan name…'} value={planName} onChange={e=>{setPlanName(e.target.value);setPlanDirty(true);}} style={{minWidth:180,flex:'1 1 200px',boxSizing:'border-box'}}/>
          <button className="btn-icone" onClick={newPlan} title={lang==='pt'?'novo plano em branco':'new blank plan'}><Icon n="plus" size={16}/></button>
          <button className="btn-icone" onClick={()=>setImportOpen(true)} title={lang==='pt'?'importar pessoas do cadastro':'import people from the registry'}>
            <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="8" r="3.4"/><path d="M2.8 20c0-3.3 2.9-5.2 6.2-5.2s6.2 1.9 6.2 5.2"/><path d="M18 8v6M15 11h6"/></svg>
          </button>
          <button className="btn-icone" onClick={importarEstadoAtual} title={lang==='pt'?'importar a arrumação atual do barco':'import the current arrangement'}>⚓</button>
          <button className="btn-icone" onClick={()=>{ fetchPlans(); setLoadOpen(true); }} title={lang==='pt'?'carregar plano salvo':'load a saved plan'}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.1" strokeLinecap="round" strokeLinejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2.4h8A2 2 0 0 1 21 9.4V17a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/></svg>
            {plans.length > 0 && <span className="badge">{plans.length}</span>}
          </button>
          {currentPlanId && (
            <button className="btn-icone neutro" onClick={deletePlan} title={lang==='pt'?'excluir este plano':'delete this plan'}>
              <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--rose)" strokeWidth="2.2" strokeLinecap="round"><path d="M4 7h16M9 7V5h6v2M7 7l1 13h8l1-13M10 11v6M14 11v6"/></svg>
            </button>
          )}
          <button className="btn-tidal" onClick={savePlan} disabled={savingPlan}>{savingPlan?(lang==='pt'?'Salvando…':'Saving…'):(lang==='pt'?'Salvar':'Save')}{planDirty?' •':''}</button>
          <div style={{flex:1,minWidth:12}}/>
          <button className="btn-tidal" onClick={()=>{ if(!Object.keys(planAssign).length){ toast.push({icon:'x', title: lang==='pt'?'O plano está vazio':'The plan is empty'}); return; } setModeChoiceOpen(true); }}>
            <Icon n="upload" size={14}/>{lang==='pt'?'Aplicar':'Apply'}
          </button>
        </div>
      )}

      {mode==='plan' && planConflitos.length > 0 && (
        <div style={{marginBottom:18,padding:'14px 18px',background:'rgba(177,78,92,0.06)',border:'1px solid var(--rose)',borderRadius:14}}>
          <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8}}>
            <span style={{fontSize:15}}>⚠️</span>
            <span style={{fontFamily:'var(--fs)',fontSize:16,color:'var(--rose)'}}>
              {lang==='pt'
                ? `${planConflitos.length} camarote${planConflitos.length>1?'s':''} com gênero misto`
                : `${planConflitos.length} cabin${planConflitos.length>1?'s':''} with mixed gender`}
            </span>
            <span style={{marginLeft:'auto',fontFamily:'var(--fm)',fontSize:10.5,color:'var(--muted)'}}>{lang==='pt'?'aviso — dá pra salvar mesmo assim':'warning — you can still save'}</span>
          </div>
          <div style={{display:'grid',gap:5}}>
            {planConflitos.map((c,i) => (
              <div key={i} style={{fontFamily:'var(--fm)',fontSize:12,color:'var(--ink)',display:'flex',flexWrap:'wrap',gap:6,alignItems:'baseline'}}>
                <span style={{fontWeight:600,color:'var(--rose)'}}>Cab {c.cabNum}{c.room!=='*'?` · ${lang==='pt'?'ala':'wing'} ${c.room}`:''}</span>
                <span style={{color:'var(--muted)'}}>{c.nomes.map(n=>`${n.nome} (${n.genero})`).join('  ·  ')}</span>
              </div>
            ))}
          </div>
        </div>
      )}

      {loading ? (
        // Skeleton loaders
        <div>
          <div style={{height:120,borderRadius:20,marginBottom:24,background:'linear-gradient(90deg,var(--cream-2) 25%,var(--cream-3) 50%,var(--cream-2) 75%)',backgroundSize:'200% 100%',animation:'shimmer 1.5s infinite'}}/>
          <div style={{display:'grid',gridTemplateColumns:isMobile?'1fr':'300px 1fr',gap:24}}>
            <div style={{height:300,borderRadius:24,background:'linear-gradient(90deg,var(--cream-2) 25%,var(--cream-3) 50%,var(--cream-2) 75%)',backgroundSize:'200% 100%',animation:'shimmer 1.5s infinite'}}/>
            <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill, minmax(186px, 1fr))',gap:12}}>
              {[1,2,3,4,5,6].map(i=>(
                <div key={i} style={{height:110,borderRadius:16,background:'linear-gradient(90deg,var(--cream-2) 25%,var(--cream-3) 50%,var(--cream-2) 75%)',backgroundSize:'200% 100%',animation:'shimmer 1.5s infinite'}}/>
              ))}
            </div>
          </div>
        </div>
      ) : (
      <>
      {/* Scrollbar fina no tom do mapa para as áreas roláveis do módulo */}
      <style>{`
        .cab-scroll{scrollbar-width:thin;scrollbar-color:#3A4A50 transparent}
        .cab-scroll::-webkit-scrollbar{width:8px}
        .cab-scroll::-webkit-scrollbar-track{background:transparent}
        .cab-scroll::-webkit-scrollbar-thumb{background:#3A4A50;border-radius:4px}
        .cab-scroll::-webkit-scrollbar-thumb:hover{background:#4A5C64}
        .cab-scroll::-webkit-scrollbar-button{display:none}
      `}</style>

      {/* Janela unificada: mapa (protagonista) + painel lateral com abas, num container só */}
      <div style={mapFull
        ? {position:'fixed',inset:0,zIndex:9000,background:'linear-gradient(180deg, #1F2A2E 0%, #2A363A 100%)',overflowY:'auto',padding: isMobile?'14px':'22px 26px',display:'flex',flexDirection:'column'}
        : {background:'linear-gradient(180deg, #1F2A2E 0%, #2A363A 100%)',borderRadius:24,padding: isMobile?'16px':'22px 26px',display:'flex',flexDirection: isMobile?'column':'row',gap: isMobile?16:22,boxShadow:'var(--shadow-md)'}}>
        {/* Mapa — planta baixa por deck */}
        <div style={mapFull
          ? {flex:'1 1 0',minHeight:0,display:'flex',flexDirection:'column'}
          : {flex:'3 1 0',minWidth:0}}>
          <DeckPlan vagas={vagas} usersById={usersById}
            filterDeck={filterDeck} setFilterDeck={setFilterDeck}
            lang={lang}
            selCabin={selCabin} onSelCabin={setSelCabin}
            dragging={dragging}
            onCabinDrop={mapaCabineDrop}
            onVagaAction={mapaVagaAction}
            onUnassign={isAdmin ? mapaUnassign : null}
            onHist={isAdmin && mode==='live' ? ((bid)=>setHistBerth(bid)) : null}
            mapFull={mapFull} onToggleFull={()=>setMapFull(v=>!v)}
            kpisSlot={kpiChips} highlight={highlightCabs}
            focusCabin={focusCabin}
            pool={unassigned}
            onPick={isAdmin ? ((uid, bid)=>{ doAssign(uid, bid); }) : null}/>
        </div>

        {!mapFull && (
        // Wrapper com altura zerada: quem dita a altura da janela é o mapa; o aside preenche
        // o espaço via position:absolute e rola internamente (sem esticar o container nem a página)
        <div style={isMobile ? {} : {flex:'1 1 0',minWidth:300,position:'relative'}}>
        <aside className="cab-scroll" style={isMobile ? {
          borderTop:'1px solid rgba(245,239,227,0.12)',paddingTop:16,paddingRight:10,
          color:'#F5EFE3',
        } : {
          position:'absolute',inset:0,overflowY:'auto',overflowX:'hidden',
          borderLeft:'1px solid rgba(245,239,227,0.12)',
          paddingLeft:22,paddingRight:10,
          color:'#F5EFE3',
        }}>
          {/* Abas internas do painel */}
          <div style={{display:'flex',background:'rgba(245,239,227,0.07)',borderRadius:12,padding:4,gap:4,marginBottom:12}}>
            {[{id:'acomodar', l:`${lang==='pt'?'A acomodar':'To assign'} (${unassigned.length})`},{id:'plano', l:lang==='pt'?'Plano':'Plan'}].map(tb=>(
              <button key={tb.id}
                onClick={()=>setPanelTab(tb.id)}
                style={{flex:1,padding:'7px 10px',borderRadius:9,border:'none',cursor:'pointer',fontFamily:'var(--fm)',fontSize:11.5,letterSpacing:0.3,
                  background: panelTab===tb.id ? 'rgba(245,239,227,0.16)' : 'transparent',
                  color: panelTab===tb.id ? '#F5EFE3' : 'rgba(245,239,227,0.5)',
                  transition:'all .15s'}}>
                {tb.l}
              </button>
            ))}
          </div>
          {/* Busca compartilhada pelas abas + filtro de sexo (⚤→♀→♂) */}
          <div style={{display:'flex',gap:8,marginBottom:12,alignItems:'center'}}>
            <input className="input" placeholder={lang==='pt'?'buscar nome, ID ou empresa…':'search name, ID or company…'} value={search} onChange={e=>setSearch(e.target.value)}
              style={{flex:1,minWidth:0,background:'rgba(245,239,227,0.08)',border:'1px solid rgba(245,239,227,0.16)',color:'#F5EFE3'}}/>
            <button onClick={()=>setFilterSexo(s=> s==='all'?'F' : s==='F'?'M' : 'all')}
              title={lang==='pt'?'filtrar por sexo (todos → ♀ → ♂)':'filter by sex (all → ♀ → ♂)'}
              style={{width:36,height:36,flexShrink:0,borderRadius:10,cursor:'pointer',fontSize:15,lineHeight:1,
                border:`1px solid ${filterSexo==='F'?'#C36474':filterSexo==='M'?'#7FA8C9':'rgba(245,239,227,0.16)'}`,
                background: filterSexo==='F'?'rgba(195,100,116,0.28)':filterSexo==='M'?'rgba(127,168,201,0.28)':'rgba(245,239,227,0.08)',
                color:'#F5EFE3'}}>
              {filterSexo==='F'?'♀':filterSexo==='M'?'♂':'⚤'}
            </button>
          </div>
          {panelTab==='acomodar' ? (
          <div
            onDragOver={e=>{ const inPlace = dragging && (mode==='plan' ? placedInPlan.has(dragging) : usersById[dragging]?.cabine_atual); if(inPlace){ e.preventDefault(); if(!overUnassign) setOverUnassign(true); } }}
            onDragLeave={e=>{ if(!e.currentTarget.contains(e.relatedTarget)) setOverUnassign(false); }}
            onDrop={e=>{ e.preventDefault(); const uid=dragging; const inPlace = uid && (mode==='plan' ? placedInPlan.has(uid) : usersById[uid]?.cabine_atual); setOverUnassign(false); setDragging(null); setDropTarget(null); if(inPlace) doRemove(uid); }}
            style={{
            borderRadius:14,
            background: overUnassign ? 'rgba(217,105,75,0.12)' : 'transparent',
            outline: overUnassign ? '1px dashed var(--accent)' : 'none',
            transition:'background .15s'
          }}>
          <div style={{display:'flex',alignItems:'baseline',justifyContent:'space-between',marginBottom:14,color:'#F5EFE3'}}>
            <div style={{fontFamily:'var(--fs)',fontSize:22,letterSpacing:'-0.3px'}}>
              {lang==='pt'?'A':'To'} <em style={{fontStyle:'italic',color:'var(--accent)'}}>{lang==='pt'?'acomodar':'assign'}</em>
            </div>
            <Pill tone="coral">{unassigned.length}</Pill>
          </div>
          {dragging && (mode==='plan' ? placedInPlan.has(dragging) : usersById[dragging]?.cabine_atual) && (
            <div style={{fontSize:11.5,fontFamily:'var(--fm)',color:'var(--accent-2)',background:'rgba(217,105,75,0.08)',border:'1px dashed var(--accent)',borderRadius:10,padding:'8px 10px',marginBottom:10,textAlign:'center'}}>
              {lang==='pt'?'↩ solte aqui para tirar da cabine':'↩ drop here to unassign'}
            </div>
          )}

          {/* Filtro rápido por função */}
          <div style={{display:'flex',gap:6,flexWrap:'wrap',marginBottom:10}}>
            {[
              {id:'all', pt:'Todos', en:'All'},
              {id:'crew', pt:'Tripulação', en:'Crew'},
              {id:'ops', pt:'Operações', en:'Ops'},
              {id:'catering', pt:'Catering', en:'Catering'},
              {id:'terceirizada', pt:'Terceirizada', en:'Contracted'}
            ].map(r => (
              <button key={r.id}
                onClick={()=>setFilterRole(r.id)}
                style={{
                  padding:'5px 10px', borderRadius:20, border:'1px solid rgba(245,239,227,0.14)',
                  background: filterRole===r.id ? 'var(--accent)' : 'rgba(245,239,227,0.08)',
                  color: filterRole===r.id ? '#fff' : 'rgba(245,239,227,0.55)',
                  fontFamily:'var(--fm)', fontSize:11, fontWeight:500, cursor:'pointer',
                  transition:'all .15s'
                }}>
                {lang==='pt'?r.pt:r.en}
              </button>
            ))}
          </div>

          {['crew','ops','catering','terceirizada'].filter(role => filterRole==='all' || role===filterRole).map(role => {
            const q = search.toLowerCase();
            const list = unassigned.filter(u => u.role === role &&
              (filterSexo==='all' || u.genero===filterSexo) &&
              (!search || u.nome.toLowerCase().includes(q) || u.genesis_id.toLowerCase().includes(q) || (u.empresa||'').toLowerCase().includes(q)));
            if (!list.length) return null;
            const labels = lang==='pt'
              ? {admin:'Administração', crew:'Tripulação', ops:'Operações', catering:'Catering', terceirizada:'Terceirizada'}
              : {admin:'Admin', crew:'Crew', ops:'Operations'};
            return (
              <div key={role}>
                <div style={{fontFamily:'var(--fs)',fontStyle:'italic',fontSize:14,color:'rgba(245,239,227,0.55)',margin:'14px 0 4px',display:'flex',justifyContent:'space-between'}}>
                  <span>{labels[role]}</span>
                  <span style={{fontFamily:'var(--fm)',fontSize:10,color:'rgba(245,239,227,0.35)',fontStyle:'normal'}}>{list.length}</span>
                </div>
                {list.map(u => {
                  const turno = u.turno && TURNOS[u.turno];
                  return (
                    <div key={u.id}
                      draggable
                      onDragStart={e=>{setDragging(u.id);e.dataTransfer.effectAllowed='move'}}
                      onDragEnd={()=>{setDragging(null);setDropTarget(null)}}
                      onClick={()=>setDragging(prev => prev===u.id ? null : u.id)}
                      onMouseEnter={()=>setHoveredUser(u.id)}
                      onMouseLeave={()=>setHoveredUser(null)}
                      title={lang==='pt'?'Arraste para uma vaga':'Drag to a berth'}
                      style={{
                        display:'flex',alignItems:'center',gap:10,padding:'9px 10px',borderRadius:14,
                        cursor: dragging===u.id ? 'grabbing' : 'grab',
                        transition:'all .15s',
                        userSelect:'none', WebkitUserSelect:'none', MozUserSelect:'none',
                        outline: dragging===u.id ? '2px solid var(--accent)' : 'none',
                        background: dragging===u.id ? 'rgba(245,239,227,0.1)' : (hoveredUser===u.id ? 'rgba(245,239,227,0.08)' : 'transparent')
                      }}>
                      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="rgba(245,239,227,0.4)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{flexShrink:0, opacity:0.7}}>
                        <line x1="8" y1="6" x2="21" y2="6"/>
                        <line x1="8" y1="12" x2="21" y2="12"/>
                        <line x1="8" y1="18" x2="21" y2="18"/>
                        <line x1="3" y1="6" x2="3.01" y2="6"/>
                        <line x1="3" y1="12" x2="3.01" y2="12"/>
                        <line x1="3" y1="18" x2="3.01" y2="18"/>
                      </svg>
                      <Avatar user={u} size="sm"/>
                      <div style={{flex:1,minWidth:0,color:'#F5EFE3'}}>
                        <div style={{fontSize:13.5,fontWeight:500,lineHeight:1.2,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis',display:'flex',alignItems:'center',gap:6}}>
                          {u.nome}
                          {u.genero==='F' && <span style={{fontSize:11,color:'#C36474'}}>♀</span>}
                        </div>
                        <div style={{fontSize:11,color:'rgba(245,239,227,0.5)',fontFamily:'var(--fm)',letterSpacing:0.2,marginTop:1,display:'flex',alignItems:'center',gap:6}}>
                          {turno && (
                            <span style={{display:'inline-flex',alignItems:'center',gap:4}}>
                              <span style={{width:6,height:6,borderRadius:'50%',background:turno.color}}/>
                              {turno.short}
                            </span>
                          )}
                          {u.empresa}
                        </div>
                      </div>
                      {mode==='plan' && <button onClick={(e)=>{e.stopPropagation(); removerDoPlano(u.id);}} title={lang==='pt'?'remover do plano':'remove from plan'} style={{flexShrink:0,border:'none',background:'transparent',cursor:'pointer',color:'rgba(245,239,227,0.5)',fontSize:16,lineHeight:1,padding:'2px 4px'}}>×</button>}
                    </div>
                  );
                })}
              </div>
            );
          })}
          {!unassigned.length && (
            <div className="empty" style={{padding:'24px 12px',fontSize:15,color:'rgba(245,239,227,0.5)'}}>
              {lang==='pt'?'todos acomodados ✓':'all assigned ✓'}
            </div>
          )}
          </div>
          ) : (
          /* Aba Plano — ocupação por cabine, agrupada por deck (espelha o mapa) */
          <div>
            {/* Exportar relatório de ocupação (realocado da seção antiga) */}
            <div style={{display:'flex',justifyContent:'flex-end',marginBottom:10}}>
              <button onClick={exportPDF} title={lang==='pt'?'exportar relatório de ocupação (PDF)':'export occupancy report (PDF)'}
                style={{display:'inline-flex',alignItems:'center',gap:6,padding:'6px 11px',borderRadius:9,cursor:'pointer',fontFamily:'var(--fm)',fontSize:11,border:'1px solid rgba(245,239,227,0.16)',background:'rgba(245,239,227,0.08)',color:'#F5EFE3'}}>
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#F5EFE3" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3v12m0 0l-4.5-4.5M12 15l4.5-4.5M4 20h16"/></svg>
                {lang==='pt'?'Exportar PDF':'Export PDF'}
              </button>
            </div>
            {[9,8,7,6].filter(dk => (planoPorDeck[dk]||[]).length).map(dk => (
              <div key={dk} style={{marginBottom:16}}>
                <div style={{fontFamily:'var(--fs)',fontStyle:'italic',fontSize:14,color:'rgba(245,239,227,0.55)',margin:'0 0 6px',display:'flex',justifyContent:'space-between'}}>
                  <span>D{dk} · {(lang==='pt'?{9:'residencial',8:'tripulação',7:'misto',6:'operações'}:{9:'residential',8:'crew',7:'mixed',6:'operations'})[dk]}</span>
                  <span style={{fontFamily:'var(--fm)',fontSize:10,color:'rgba(245,239,227,0.35)',fontStyle:'normal'}}>{planoPorDeck[dk].length}</span>
                </div>
                <div style={{display:'flex',flexDirection:'column',gap:8}}>
                  {planoPorDeck[dk].map(c => {
                    const occN = c.berths.filter(b=>b.user).length;
                    const totN = c.berths.length;
                    const cor = occN===0 ? '#7BB67E' : occN>=totN ? '#C0392B' : '#E8B453';
                    return (
                      <div key={c.num}
                        onClick={()=>setFocusCabin({ num: c.num, ts: Date.now() })}
                        onMouseEnter={()=>setPlanoHover(String(c.num))}
                        onMouseLeave={()=>setPlanoHover(null)}
                        title={lang==='pt'?'localizar no mapa':'locate on the map'}
                        style={{background: planoHover===String(c.num)?'rgba(245,239,227,0.11)':'rgba(245,239,227,0.06)',border:'1px solid rgba(245,239,227,0.1)',borderLeft:`3px solid ${cor}`,borderRadius:12,padding:'9px 12px',cursor:'pointer',transition:'background .15s'}}>
                        <div style={{display:'flex',alignItems:'baseline',justifyContent:'space-between',marginBottom:4,gap:8}}>
                          <span style={{fontFamily:'var(--fm)',fontSize:12,fontWeight:600,color:'#F5EFE3',minWidth:0,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>Cab {c.num} <span style={{color:'rgba(245,239,227,0.45)',fontWeight:400}}>· {c.tipo}</span></span>
                          <span style={{fontFamily:'var(--fm)',fontSize:10.5,color:'rgba(245,239,227,0.5)',flexShrink:0}}>{occN}/{totN}</span>
                        </div>
                        {c.berthsView.map(b => {
                          const u = b.u;
                          const turno = u && u.turno && TURNOS[u.turno];
                          return (
                            <div key={b.id} style={{display:'flex',alignItems:'center',gap:6,fontFamily:'var(--fm)',fontSize:11.5,color:'#F5EFE3',padding:'2px 0'}}>
                              <span style={{color:'rgba(245,239,227,0.4)',flexShrink:0}}>{b.id}</span>
                              {u ? (
                                <span style={{flex:1,minWidth:0,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'}}>
                                  {u.nome}{u.genero==='F' && <span style={{color:'#C36474'}}> ♀</span>}
                                </span>
                              ) : (
                                <span style={{flex:1,fontStyle:'italic',color:'rgba(245,239,227,0.35)'}}>{lang==='pt'?'livre':'free'}</span>
                              )}
                              {turno && <span style={{width:6,height:6,borderRadius:'50%',background:turno.color,flexShrink:0}}/>}
                            </div>
                          );
                        })}
                      </div>
                    );
                  })}
                </div>
              </div>
            ))}
            {!Object.keys(planoPorDeck).length && (
              <div className="empty" style={{padding:'24px 12px',fontSize:14,color:'rgba(245,239,227,0.5)'}}>
                {lang==='pt'?'nenhuma cabine/pessoa casa com a busca.':'no cabin/person matches the search.'}
              </div>
            )}
          </div>
          )}
        </aside>
        </div>
        )}
      </div>

      {dragging && usersById[dragging] && (
        <div style={{position:'fixed',bottom:18,left:'50%',transform:'translateX(-50%)',zIndex:8150,display:'flex',alignItems:'center',gap:10,background:'var(--ink)',color:'var(--cream)',borderRadius:999,padding:'9px 10px 9px 18px',boxShadow:'var(--shadow-lg)',fontFamily:'var(--fm)',fontSize:12}}>
          {lang==='pt'?'movendo':'moving'} <b>{usersById[dragging].nome}</b> — {lang==='pt'?'toque na vaga de destino':'tap the target berth'}
          <button onClick={()=>{setDragging(null);setDropTarget(null);}} style={{border:'none',borderRadius:999,padding:'5px 12px',cursor:'pointer',background:'var(--cream)',color:'var(--ink)',fontFamily:'var(--fm)',fontSize:11}}>{lang==='pt'?'cancelar':'cancel'}</button>
        </div>
      )}
      {swapAsk && (() => {
        const uIn = usersById[swapAsk.userId];
        const uOut = usersById[swapAsk.vaga.user];
        const antiga = mode==='plan'
          ? (Object.keys(planAssign).find(b => String(planAssign[b]) === String(swapAsk.userId)) || null)
          : berthDe(swapAsk.userId);
        return (
          <SwapModal
            nomeEntra={uIn ? uIn.nome : '?'} nomeSai={uOut ? uOut.nome : '?'}
            vaga={swapAsk.vaga.id} vagaAntiga={antiga} modoPlan={mode==='plan'} lang={lang}
            onTrocar={()=>{ mode==='plan' ? swapPlan(swapAsk.userId, swapAsk.vaga, true) : swapLive(swapAsk.userId, swapAsk.vaga, true); setSwapAsk(null); }}
            onSair={()=>{ mode==='plan' ? swapPlan(swapAsk.userId, swapAsk.vaga, false) : swapLive(swapAsk.userId, swapAsk.vaga, false); setSwapAsk(null); }}
            onClose={()=>{ setSwapAsk(null); setDragging(null); setDropTarget(null); }}
          />
        );
      })()}
      {histBerth && <BerthHistModal berthId={histBerth} token={token} lang={lang} onClose={()=>setHistBerth(null)}/>}
      {managing && (
        <CabinManager state={state} setState={setState} lang={lang} toast={toast}
          onClose={()=>setManaging(false)} usersById={usersById}
          cabins={cabins} fetchData={fetchData}/>
      )}

      {importOpen && (
        <ImportPeopleModal
          users={users}
          jaNoPlano={planPool}
          onImport={importarPessoas}
          onClose={()=>setImportOpen(false)}
          lang={lang} isMobile={isMobile}
        />
      )}
      {filtroOpen && (
        <CabinFiltroModal
          filterDeck={filterDeck} setFilterDeck={setFilterDeck}
          filterTurno={filterTurno} setFilterTurno={setFilterTurno}
          filterEmpresa={filterEmpresa} setFilterEmpresa={setFilterEmpresa}
          filterGenero={filterGenero} setFilterGenero={setFilterGenero}
          filterStatus={filterStatus} setFilterStatus={setFilterStatus}
          allDecks={allDecks} empresas={empresas} lang={lang}
          onLimpar={()=>{ setFilterDeck('all'); setFilterTurno('all'); setFilterEmpresa('all'); setFilterGenero('all'); setFilterStatus('all'); setFilterRole('all'); }}
          onClose={()=>setFiltroOpen(false)}
        />
      )}
      {modeChoiceOpen && (
        <PlanModeModal
          temLazy={(intents||[]).length > 0}
          lang={lang}
          onClose={()=>setModeChoiceOpen(false)}
          onAgora={()=>{ setModeChoiceOpen(false); setApplyOpen(true); }}
          onReativo={armarLazy}
        />
      )}
      {applyOpen && (
        <ApplyPlanModal
          planAssign={planAssign} usersById={usersById} users={users} planPool={planPool}
          token={token} toast={toast} lang={lang} isMobile={isMobile}
          onClose={()=>setApplyOpen(false)}
          onApplied={()=>{ setApplyOpen(false); setMode('live'); fetchData(); }}
        />
      )}

      {loadOpen && (
        <LoadPlanModal
          plans={plans} currentPlanId={currentPlanId} lang={lang} isMobile={isMobile}
          onLoad={(id)=>{ loadPlan(id); setLoadOpen(false); }}
          onDelete={deletePlanById}
          onClose={()=>setLoadOpen(false)}
        />
      )}

      {genderAlert && (
        <div style={{position:'fixed',inset:0,zIndex:200,background:'rgba(31,42,46,0.55)',backdropFilter:'blur(3px)',display:'flex',alignItems:'center',justifyContent:'center',padding:'5vh 18px'}}
          onClick={()=>setGenderAlert(null)}>
          <div onClick={e=>e.stopPropagation()} style={{
            width:'100%',maxWidth:420,background:'var(--surface)',borderRadius:22,padding:'28px 26px',
            boxShadow:'0 24px 60px -16px rgba(31,42,46,.45)',textAlign:'center',animation:'slideIn .25s both'
          }}>
            <div style={{fontSize:40,lineHeight:1,marginBottom:12}}>⚠️</div>
            <h3 style={{fontFamily:'var(--fs)',fontSize:22,letterSpacing:'-0.4px',marginBottom:10}}>
              {lang==='pt'?'Gêneros diferentes no camarote':'Different genders in room'}
            </h3>
            <p style={{fontFamily:'var(--fm)',fontSize:13.5,color:'var(--muted)',lineHeight:1.55,marginBottom:6}}>
              {lang==='pt'
                ? <>Você está alocando <b style={{color:'var(--ink)'}}>{genderAlert.nome}</b> na vaga <b style={{color:'var(--ink)'}}>{genderAlert.vaga}</b>, que já tem ocupante de gênero diferente no mesmo quarto.</>
                : <>You are assigning <b style={{color:'var(--ink)'}}>{genderAlert.nome}</b> to berth <b style={{color:'var(--ink)'}}>{genderAlert.vaga}</b>, which already has an occupant of a different gender in the same room.</>}
            </p>
            <p style={{fontFamily:'var(--fm)',fontSize:12,color:'var(--muted-2)',marginBottom:22}}>
              {lang==='pt'?'A alocação foi mantida. Confirme que você está ciente.':'The assignment was kept. Please confirm you are aware.'}
            </p>
            <button onClick={()=>setGenderAlert(null)} style={{
              width:'100%',padding:'13px',borderRadius:13,border:'none',cursor:'pointer',
              background:'var(--accent)',color:'#fff',fontFamily:'var(--fm)',fontSize:14,fontWeight:600
            }}>
              OK, estou ciente
            </button>
          </div>
        </div>
      )}
      </>
      )}
    </main>
  );
};

Object.assign(window, {Cabines});
