// v-shell.jsx — Lasso vendor dashboard chrome: sidebar nav, topbar, app state, toasts

const NAV = [
  { id:'overview',  label:'Overview',          icon:I.grid },
  { id:'map',       label:'Map Campaigns',     icon:I.map },
  { id:'menu',      label:'Menu Manager',      icon:I.utensils },
  { id:'orders',    label:'Orders & Sales',    icon:I.receipt },
  { id:'insights',  label:'Customer Insights', icon:I.users },
  { id:'campaigns', label:'Campaigns',         icon:I.megaphone },
  { id:'trail',     label:'Trail Studio',      icon:I.sparkle },
  { id:'rewards',   label:'Rewards',           icon:I.trophy },
  { id:'team',      label:'Team & Media',      icon:I.image },
  { id:'ops',       label:'Back Office',       icon:I.layers, soon:true },
  { id:'settings',  label:'Settings',          icon:I.settings },
];

// ─── Sidebar (floating frosted-glass nav) ────────────────────────────
function Sidebar({ app }) {
  const xpPct = Math.round(VENDOR.xp / VENDOR.xpToNext * 100);
  return (
    <div style={{ width:'100%', height:'100%', background:'rgba(27,18,13,0.60)', backdropFilter:'blur(22px) saturate(140%)', WebkitBackdropFilter:'blur(22px) saturate(140%)', color:'var(--dust-cream)', display:'flex', flexDirection:'column', padding:'20px 14px', borderRadius:22, border:'1px solid rgba(255,255,255,0.12)', boxShadow:'0 24px 60px -18px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.08)' }}>
      <div style={{ display:'flex', alignItems:'center', gap:9, padding:'0 8px 4px' }}>
        <Wordmark size={18}/>
        <span style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:10, letterSpacing:'0.14em', textTransform:'uppercase', color:'#9F8979', border:'1px solid rgba(255,255,255,0.14)', borderRadius:6, padding:'2px 6px' }}>Vendor</span>
      </div>

      {/* Vendor card */}
      <div style={{ margin:'18px 4px 16px', padding:'13px 13px', background:'rgba(255,255,255,0.04)', border:'1px solid rgba(255,255,255,0.07)', borderRadius:14 }}>
        <div style={{ display:'flex', alignItems:'center', gap:10 }}>
          <div style={{ width:38, height:38, borderRadius:11, background:'linear-gradient(135deg,#F28C1B,#D85A14)', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'var(--font-display)', fontWeight:900, fontSize:14, color:'#fff', flexShrink:0 }}>{VENDOR.initials}</div>
          <div style={{ minWidth:0 }}>
            <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:13.5, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{VENDOR.name}</div>
            <div style={{ fontFamily:'var(--font-body)', fontSize:10.5, color:'#9F8979' }}>{VENDOR.location}</div>
          </div>
        </div>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginTop:12, marginBottom:5 }}>
          <span style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:10.5, letterSpacing:'0.04em', color:'#E9B97A' }}>LEVEL {VENDOR.level}</span>
          <span style={{ fontFamily:'var(--font-mono)', fontSize:10.5, color:'#9F8979' }}>{VENDOR.xp.toLocaleString()} / {VENDOR.xpToNext.toLocaleString()} XP</span>
        </div>
        <Bar pct={xpPct} color="linear-gradient(90deg,#F28C1B,#D85A14)" h={6} track="rgba(255,255,255,0.10)"/>
      </div>

      <div style={{ display:'flex', flexDirection:'column', gap:2, overflowY:'auto', minHeight:0, flex:1 }}>
        {NAV.filter(n=>roleCanSee(app.role, n.id)).map(n=>{
          const on = app.page===n.id;
          return (
            <button key={n.id} onClick={()=>app.nav(n.id)} style={{
              display:'flex', alignItems:'center', gap:11, width:'100%', padding:'10px 12px', borderRadius:11, border:'none', cursor:'pointer',
              background: on ? 'rgba(242,140,27,0.14)' : 'transparent', color: on ? 'var(--dust-cream)' : '#A8978A',
              fontFamily:'var(--font-display)', fontWeight:700, fontSize:13.5, textAlign:'left', transition:'background 150ms', position:'relative',
            }}
            onMouseEnter={(e)=>{ if(!on) e.currentTarget.style.background='rgba(255,255,255,0.04)'; }}
            onMouseLeave={(e)=>{ if(!on) e.currentTarget.style.background='transparent'; }}>
              {on && <span style={{ position:'absolute', left:0, top:9, bottom:9, width:3, borderRadius:999, background:'var(--brand-orange)' }}/>}
              <n.icon c={on?'var(--brand-orange)':'#8A786B'} s={19} w={on?2.2:2}/> {n.label}
              {n.soon && <span style={{ marginLeft:'auto', fontFamily:'var(--font-display)', fontWeight:800, fontSize:8.5, letterSpacing:'0.08em', textTransform:'uppercase', color:'#a86d12', background:'rgba(224,153,42,0.16)', border:'1px solid rgba(224,153,42,0.3)', borderRadius:5, padding:'2px 5px' }}>Soon</span>}
            </button>
          );
        })}
      </div>

      <div style={{ marginTop:12, paddingTop:14, borderTop:'1px solid rgba(255,255,255,0.08)' }}>
        <div style={{ display:'flex', alignItems:'center', gap:9, padding:'2px 6px' }}>
          <I.shield c="#5B8F6E" s={17}/>
          <div style={{ fontFamily:'var(--font-body)', fontSize:10.5, color:'#9F8979', lineHeight:1.35 }}>Customer data is aggregated. Addresses are never shown.</div>
        </div>
      </div>
    </div>
  );
}

// ─── Topbar ──────────────────────────────────────────────────────────
function Topbar({ app, floating=false }) {
  const titles = {
    overview:'Overview', map:'Map Campaigns', menu:'Menu Manager', orders:'Orders & Sales',
    insights:'Customer Insights', campaigns:'Campaigns', rewards:'Rewards', trail:'Trail Studio',
    team:'Team & Media', ops:'Back Office', settings:'Settings',
  };
  const base = { height:floating?60:66, flexShrink:0, display:'flex', alignItems:'center', gap:16, padding:'0 18px 0 22px', zIndex:20 };
  const skin = floating
    ? { background:'rgba(255,255,255,0.5)', backdropFilter:'blur(22px) saturate(150%)', WebkitBackdropFilter:'blur(22px) saturate(150%)', borderRadius:18, border:'1px solid rgba(255,255,255,0.5)', boxShadow:'0 18px 44px -16px rgba(27,18,13,0.45)' }
    : { padding:'0 26px', borderBottom:'1px solid var(--soft-line)', background:'var(--bg-app)' };
  return (
    <div style={{ ...base, ...skin }}>
      <div style={{ display:'flex', alignItems:'center', gap:10 }}>
        <span style={{ fontFamily:'var(--font-display)', fontWeight:900, fontSize:17, color:'var(--fg-primary)', letterSpacing:'-0.01em' }}>{titles[app.page]}</span>
        <Pill tone="green" dot>Open · accepting orders</Pill>
      </div>

      <div onMouseDown={(e)=>{ e.preventDefault(); app.openPalette && app.openPalette(); }} role="button" style={{ flex:1, maxWidth:380, marginLeft:8, display:'flex', alignItems:'center', gap:9, height:42, cursor:'pointer', background: floating?'rgba(255,255,255,0.5)':'var(--bg-surface)', border:'1px solid', borderColor: floating?'rgba(255,255,255,0.5)':'var(--soft-line)', borderRadius:12, padding:'0 13px', boxShadow:'var(--shadow-sm)' }}>
        <I.search c="var(--warm-stone)" s={17}/>
        <input placeholder="Search estates, menu items, campaigns…" readOnly style={{ border:'none', outline:'none', background:'transparent', flex:1, fontFamily:'var(--font-body)', fontSize:13.5, color:'var(--fg-primary)', cursor:'pointer', pointerEvents:'none' }}/>
        <kbd style={{ fontFamily:'var(--font-mono)', fontSize:10.5, color:'var(--warm-stone)', background:'rgba(27,18,13,0.05)', borderRadius:5, padding:'2px 6px' }}>⌘K</kbd>
      </div>

      <div style={{ marginLeft:'auto', display:'flex', alignItems:'center', gap:12 }}>
        <Pill tone="orange" icon={<I.flame c="#c0500f" s={13} f/>} style={{ fontSize:11.5, padding:'5px 11px' }}>{`${VENDOR.streak}-week streak`}</Pill>
        <Btn kind="accent" size="md" icon={<I.plus c="#fff" s={16}/>} onClick={()=>app.openBuilder(null)}>New campaign</Btn>
        <NotificationsMenu app={app}/>
        <SessionMenu app={app}/>
      </div>
    </div>
  );
}

// ─── Notifications — action items from real in-app pending state ─────
const NOTIF_ITEMS = [
  { id:'n1', icon:'utensils',  text:'2 menu items are switched off',            sub:'Garden Veg Stonebake · Dip Trio',          nav:'menu' },
  { id:'n2', icon:'users',     text:'2 staff invites still pending',            sub:'sofia.r… · jake.kitchen…',                 nav:'team' },
  { id:'n3', icon:'shield',    text:'Document verification in review',          sub:'Payout verification — admin flow pending',  nav:'team' },
  { id:'n4', icon:'image',     text:'1 media asset still processing',           sub:'loaded-fries-close.jpg',                    nav:'team' },
  { id:'n5', icon:'megaphone', text:'“Wolverton Win-Back” ends today',           sub:'Extend it or let it close',                 nav:'campaigns' },
];
function NotificationsMenu({ app }) {
  const [open, setOpen] = React.useState(false);
  const [items, setItems] = React.useState(NOTIF_ITEMS);
  const ref = React.useRef(null);
  React.useEffect(()=>{
    if (!open) return;
    const close = (e)=>{ if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return ()=>document.removeEventListener('mousedown', close);
  }, [open]);
  return (
    <div ref={ref} style={{ position:'relative', flexShrink:0 }}>
      <button data-notifications-trigger="" onClick={()=>setOpen(o=>!o)} title="Action items" style={{
        width:40, height:40, borderRadius:12, border:'1px solid rgba(255,255,255,0.55)',
        background: open ? 'var(--bg-warm)' : 'rgba(255,251,245,0.62)', backdropFilter:'blur(10px) saturate(140%)', WebkitBackdropFilter:'blur(10px) saturate(140%)',
        cursor:'pointer', position:'relative', display:'flex', alignItems:'center', justifyContent:'center',
        boxShadow:'0 6px 18px -10px rgba(27,18,13,0.35), inset 0 1px 0 rgba(255,255,255,0.6)' }}>
        <I.bell c="var(--fg-primary)" s={18}/>
        {items.length>0 && <span style={{ position:'absolute', top:8, right:9, width:8, height:8, borderRadius:'50%', background:'var(--brand-orange)', border:'2px solid var(--bg-surface)' }}/>}
      </button>
      {open && (
        <div data-notifications-panel="" style={{ position:'absolute', top:48, right:0, width:312, background:'var(--bg-surface)', border:'1px solid var(--soft-line)', borderRadius:16, boxShadow:'var(--shadow-lg)', zIndex:200, overflow:'hidden', animation:'v-pop 180ms var(--ease-out)' }}>
          <div style={{ padding:'13px 16px 11px', borderBottom:'1px solid var(--soft-line)', display:'flex', alignItems:'center', justifyContent:'space-between' }}>
            <Eyebrow><I.bell c="var(--brand-orange)" s={12}/> &nbsp;Action items</Eyebrow>
            {items.length>0 && <span onClick={()=>setItems([])} style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:11, color:'var(--warm-stone)', cursor:'pointer' }}>Clear all</span>}
          </div>
          {items.length===0 ? (
            <div data-notification-empty="" style={{ padding:'28px 16px', textAlign:'center' }}>
              <I.check c="var(--live-green)" s={22}/>
              <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:13, color:'var(--fg-primary)', marginTop:7 }}>All clear</div>
              <div style={{ fontFamily:'var(--font-body)', fontSize:11, color:'var(--warm-stone)', marginTop:3 }}>Nothing needs your attention right now.</div>
            </div>
          ) : (
            <div style={{ padding:'6px' }}>
              {items.map(n=>{
                const Icon = I[n.icon] || I.info;
                return (
                  <button key={n.id} onClick={()=>{ setOpen(false); app.nav(n.nav); }} style={{ display:'flex', alignItems:'flex-start', gap:10, width:'100%', padding:'9px 10px', borderRadius:10, border:'none', cursor:'pointer', background:'transparent', textAlign:'left' }}
                    onMouseEnter={e=>e.currentTarget.style.background='var(--bg-warm)'} onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                    <span style={{ width:30, height:30, borderRadius:9, background:'var(--bg-warm)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}><Icon c="var(--horse-brown)" s={14}/></span>
                    <span style={{ minWidth:0 }}>
                      <span style={{ display:'block', fontFamily:'var(--font-display)', fontWeight:700, fontSize:12.5, color:'var(--fg-primary)' }}>{n.text}</span>
                      <span style={{ display:'block', fontFamily:'var(--font-body)', fontSize:10.5, color:'var(--warm-stone)', marginTop:1 }}>{n.sub}</span>
                    </span>
                  </button>
                );
              })}
              <div style={{ padding:'8px 10px 6px', fontFamily:'var(--font-body)', fontSize:9.5, color:'var(--warm-stone)', borderTop:'1px solid var(--soft-line)', marginTop:4 }}>Derived from live dashboard state — no push/notification feed contract yet.</div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── Session menu (avatar → account, role, expiry, sign-out) ────────
function SessionMenu({ app }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  const s = app.session.sess;
  React.useEffect(()=>{
    if (!open) return;
    const close = (e)=>{ if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return ()=>document.removeEventListener('mousedown', close);
  }, [open]);
  if (!s) return null;
  const r = ROLES[s.role];
  const roleTone = s.role==='owner'?'orange':s.role==='manager'?'blue':'grey';
  return (
    <div ref={ref} style={{ position:'relative', flexShrink:0 }}>
      <div data-user-menu-trigger="" onClick={()=>setOpen(o=>!o)} title={`${s.name} · ${r.label}`} style={{ width:40, height:40, borderRadius:'50%', background:'linear-gradient(135deg,#F28C1B,#D85A14)', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'var(--font-display)', fontWeight:900, fontSize:14, color:'#fff', cursor:'pointer', boxShadow: open?'0 0 0 3px rgba(242,140,27,0.35)':'none' }}>{s.init}</div>
      {open && (
        <div data-user-menu="" style={{ position:'absolute', top:48, right:0, width:264, background:'var(--bg-surface)', border:'1px solid var(--soft-line)', borderRadius:16, boxShadow:'var(--shadow-lg)', zIndex:200, overflow:'hidden', animation:'v-pop 180ms var(--ease-out)' }}>
          <div style={{ padding:'15px 16px 13px', borderBottom:'1px solid var(--soft-line)' }}>
            <div style={{ display:'flex', alignItems:'center', gap:11 }}>
              <div style={{ width:38, height:38, borderRadius:11, background:'linear-gradient(135deg,#F28C1B,#D85A14)', display:'flex', alignItems:'center', justifyContent:'center', fontFamily:'var(--font-display)', fontWeight:900, fontSize:13, color:'#fff', flexShrink:0 }}>{s.init}</div>
              <div style={{ minWidth:0 }}>
                <div style={{ fontFamily:'var(--font-display)', fontWeight:800, fontSize:13.5, color:'var(--fg-primary)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{s.name}</div>
                <div style={{ fontFamily:'var(--font-body)', fontSize:10.5, color:'var(--warm-stone)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{s.email}</div>
              </div>
            </div>
            <div style={{ display:'flex', alignItems:'center', gap:7, marginTop:11 }}>
              <Pill tone={roleTone} dot>{r.label}</Pill>
              <span style={{ fontFamily:'var(--font-body)', fontSize:10.5, color:'var(--warm-stone)' }}>{r.access}</span>
            </div>
          </div>
          <div style={{ padding:'7px' }}>
            {[
              { icon:I.settings, label:'Storefront settings', act:()=>{ setOpen(false); app.nav('settings'); }, hide: !roleCanSee(app.role,'settings') },
              { icon:I.clock,    label:'Simulate session expiry', sub:'demo', act:()=>{ setOpen(false); app.session.expire(); } },
              { icon:I.logout,   label:'Sign out', danger:true, act:()=>{ setOpen(false); app.session.signOut(); } },
            ].filter(x=>!x.hide).map((x,i)=>(
              <button key={i} onClick={x.act} style={{ display:'flex', alignItems:'center', gap:10, width:'100%', padding:'9px 10px', borderRadius:10, border:'none', cursor:'pointer', background:'transparent', textAlign:'left' }}
                onMouseEnter={e=>e.currentTarget.style.background='var(--bg-warm)'} onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                <x.icon c={x.danger?'#b0341a':'var(--horse-brown)'} s={16}/>
                <span style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:12.5, color: x.danger?'#b0341a':'var(--fg-primary)' }}>{x.label}</span>
                {x.sub && <span style={{ marginLeft:'auto', fontFamily:'var(--font-mono)', fontSize:9.5, color:'var(--warm-stone)', border:'1px solid var(--soft-line)', borderRadius:5, padding:'1px 5px' }}>{x.sub}</span>}
              </button>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── Toast ───────────────────────────────────────────────────────────
function ToastHost({ toast, onDone }) {
  React.useEffect(()=>{ if(!toast) return; const t=setTimeout(onDone, 2600); return ()=>clearTimeout(t); }, [toast]);
  if (!toast) return null;
  return (
    <div style={{ position:'fixed', bottom:26, left:'50%', transform:'translateX(-50%)', zIndex:900,
      background:'var(--dark-chocolate)', color:'var(--dust-cream)', borderRadius:13, padding:'12px 18px',
      display:'flex', alignItems:'center', gap:11, boxShadow:'var(--shadow-lg)', animation:'v-toast 280ms var(--ease-out)' }}>
      <span style={{ width:8, height:8, borderRadius:'50%', background:'var(--sun-orange)' }}/>
      <span style={{ fontFamily:'var(--font-display)', fontWeight:700, fontSize:13.5 }}>{toast}</span>
    </div>
  );
}

// ─── Empty / placeholder screen (Orders, Insights, Settings stubs) ──
function ComingSoon({ icon:Icon, title, blurb, points }) {
  return (
    <div style={{ maxWidth:720 }}>
      <Card pad={0} style={{ overflow:'hidden' }}>
        <div style={{ padding:'34px 32px', display:'flex', gap:20, alignItems:'flex-start', borderBottom:'1px solid var(--soft-line)' }}>
          <div style={{ width:54, height:54, borderRadius:15, background:'var(--bg-warm)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
            <Icon c="var(--brand-orange)" s={26}/>
          </div>
          <div>
            <h2 style={{ margin:'2px 0 8px', fontFamily:'var(--font-display)', fontWeight:900, fontSize:21, color:'var(--fg-primary)' }}>{title}</h2>
            <p style={{ margin:0, fontFamily:'var(--font-body)', fontSize:14, color:'var(--warm-stone)', lineHeight:1.55, maxWidth:460 }}>{blurb}</p>
          </div>
        </div>
        <div style={{ padding:'22px 32px', display:'grid', gridTemplateColumns:'1fr 1fr', gap:'14px 28px' }}>
          {points.map((p,i)=>(
            <div key={i} style={{ display:'flex', gap:10, alignItems:'flex-start' }}>
              <I.check c="var(--live-green)" s={17}/>
              <span style={{ fontFamily:'var(--font-body)', fontSize:13.5, color:'var(--fg-primary)' }}>{p}</span>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
}

function OrdersStub() {
  return <ComingSoon icon={I.receipt} title="Orders & Sales"
    blurb="Live order feed, daily and weekly sales breakdowns, peak-hour heatmaps and payout reconciliation for Fire & Dough."
    points={['Live incoming order queue','Sales by day, item and channel','Peak-hour demand heatmap','Refunds & payout statements','Prep-time and acceptance metrics','Export to CSV / accounting']}/>;
}
function InsightsStub() {
  return <ComingSoon icon={I.users} title="Customer Insights"
    blurb="Aggregated, anonymised demand across Milton Keynes — cuisine affinity, repeat behaviour and basket trends. Never individual customers."
    points={['Estate-level demand breakdown','Cuisine affinity vs. your menu','New vs. repeat vs. dormant mix','Basket size & reorder windows','Anonymised — no addresses or pins','Feeds the Map Campaigns engine']}/>;
}
function SettingsStub() {
  return <ComingSoon icon={I.settings} title="Settings"
    blurb="Manage your storefront, opening hours, delivery zones, payouts and team access for Fire & Dough."
    points={['Storefront profile & branding','Opening hours & holidays','Delivery radius & fees','Payout account & VAT','Team members & permissions','Notification preferences']}/>;
}

// ─── Command palette (⌘K — search, routing, quick actions) ───────
function CommandPalette({ app, onClose }) {
  const [q, setQ] = React.useState('');
  const [sel, setSel] = React.useState(0);
  const inputRef = React.useRef(null);
  const listRef = React.useRef(null);
  React.useEffect(()=>{ if (inputRef.current) inputRef.current.focus(); }, []);

  const all = React.useMemo(()=>{
    const tabs = NAV.filter(n=>!n.soon && roleCanSee(app.role, n.id)).map(n=>({
      group:'Go to', label:n.label, icon:n.icon, run:()=>app.nav(n.id) }));
    const actions = [
      { group:'Actions', label:'New campaign', sub:'Open the campaign builder', icon:I.plus, run:()=>app.openBuilder(null), hide:!roleCanSee(app.role,'campaigns') },
      { group:'Actions', label:'Invite a teammate', sub:'Team & Media', icon:I.users, run:()=>app.nav('team'), hide:!roleCanSee(app.role,'team') },
      { group:'Actions', label:'Payout statements', sub:'Settings · Payouts', icon:I.receipt, run:()=>app.nav('settings'), hide:!roleCanSee(app.role,'settings') },
      { group:'Actions', label:'Storefront settings', sub:'Hours, delivery zone, profile', icon:I.settings, run:()=>app.nav('settings'), hide:!roleCanSee(app.role,'settings') },
    ].filter(a=>!a.hide);
    const estates = ESTATES.map(e=>({
      group:'Estates', label:e.name, sub:`${e.customers.toLocaleString()} customers · opportunity ${e.opportunity}`, icon:I.pin,
      run:()=>{ app.setSelected(e.id); app.nav('map'); }, hide:!roleCanSee(app.role,'map') })).filter(a=>!a.hide);
    const menu = MENU.flatMap(c=>c.items.map(it=>({
      group:'Menu items', label:it.name, sub:`${c.cat} · £${it.price.toFixed(2)}${it.avail?'':' · switched off'}`, icon:I.utensils,
      run:()=>app.nav('menu'), hide:!roleCanSee(app.role,'menu') }))).filter(a=>!a.hide);
    const camps = ACTIVE_CAMPAIGNS.map(c=>({
      group:'Campaigns', label:c.name, sub:`${c.area} · ${c.status}`, icon:I.megaphone,
      run:()=>app.openCampaign(c.id), hide:!roleCanSee(app.role,'campaigns') })).filter(a=>!a.hide);
    return { tabs, actions, estates, menu, camps };
  }, [app.role]);

  const query = q.trim().toLowerCase();
  const match = (it)=> it.label.toLowerCase().includes(query) || (it.sub||'').toLowerCase().includes(query) || it.group.toLowerCase().includes(query);
  const results = query
    ? [...all.estates, ...all.menu, ...all.camps, ...all.tabs, ...all.actions].filter(match).slice(0, 14)
    : [...all.tabs, ...all.actions];

  React.useEffect(()=>{ setSel(0); }, [q]);
  const pick = (it)=>{ onClose(); it.run(); };
  const onKey = (e)=>{
    if (e.key==='ArrowDown') { e.preventDefault(); setSel(s=>Math.min(s+1, results.length-1)); }
    else if (e.key==='ArrowUp') { e.preventDefault(); setSel(s=>Math.max(s-1, 0)); }
    else if (e.key==='Enter') { e.preventDefault(); if (results[sel]) pick(results[sel]); }
    else if (e.key==='Escape') { e.preventDefault(); onClose(); }
  };
  React.useEffect(()=>{
    const el = listRef.current && listRef.current.querySelector(`[data-cp-idx="${sel}"]`);
    if (el && listRef.current) {
      const lr = listRef.current, top = el.offsetTop, bot = top + el.offsetHeight;
      if (top < lr.scrollTop) lr.scrollTop = top - 6;
      else if (bot > lr.scrollTop + lr.clientHeight) lr.scrollTop = bot - lr.clientHeight + 6;
    }
  }, [sel, q]);

  let lastGroup = null;
  return (
    <div onMouseDown={onClose} style={{ position:'fixed', inset:0, zIndex:400, background:'rgba(27,18,13,0.44)', backdropFilter:'blur(3px)', display:'flex', justifyContent:'center', alignItems:'flex-start', paddingTop:'12vh', animation:'v-fade 160ms var(--ease-out)' }}>
      <div data-command-palette="" onMouseDown={(e)=>e.stopPropagation()} style={{ width:'100%', maxWidth:560, margin:'0 24px', background:'var(--bg-surface)', borderRadius:18, boxShadow:'var(--shadow-lg)', overflow:'hidden', animation:'v-pop 200ms var(--ease-out)' }}>
        <div style={{ display:'flex', alignItems:'center', gap:11, padding:'15px 18px', borderBottom:'1px solid var(--soft-line)' }}>
          <I.search c="var(--brand-orange)" s={18}/>
          <input ref={inputRef} value={q} onChange={e=>setQ(e.target.value)} onKeyDown={onKey} placeholder="Search or jump to…"
            style={{ border:'none', outline:'none', background:'transparent', flex:1, fontFamily:'var(--font-body)', fontSize:15, color:'var(--fg-primary)' }}/>
          <kbd style={{ fontFamily:'var(--font-mono)', fontSize:10.5, color:'var(--warm-stone)', border:'1px solid var(--soft-line)', borderRadius:5, padding:'2px 6px' }}>esc</kbd>
        </div>
        <div ref={listRef} style={{ maxHeight:380, overflowY:'auto', padding:'6px 8px 8px' }}>
          {results.length===0 && (
            <div style={{ padding:'26px 16px', textAlign:'center', fontFamily:'var(--font-body)', fontSize:12.5, color:'var(--warm-stone)' }}>No matches for “{q}”</div>
          )}
          {results.map((it,i)=>{
            const header = it.group!==lastGroup ? it.group : null; lastGroup = it.group;
            const on = i===sel;
            return (
              <React.Fragment key={it.group+it.label}>
                {header && <div style={{ padding:'9px 10px 4px', fontFamily:'var(--font-display)', fontWeight:800, fontSize:10, letterSpacing:'0.09em', textTransform:'uppercase', color:'var(--warm-stone)' }}>{header}</div>}
                <div data-cp-idx={i} onMouseEnter={()=>setSel(i)} onClick={()=>pick(it)} role="button" style={{ display:'flex', alignItems:'center', gap:11, padding:'9px 10px', borderRadius:10, cursor:'pointer', background: on?'var(--bg-warm)':'transparent' }}>
                  <span style={{ width:30, height:30, borderRadius:9, background: on?'rgba(216,90,20,0.12)':'var(--bg-app)', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}><it.icon c={on?'var(--brand-orange)':'var(--horse-brown)'} s={15}/></span>
                  <span style={{ flex:1, minWidth:0 }}>
                    <span style={{ display:'block', fontFamily:'var(--font-display)', fontWeight:700, fontSize:13, color:'var(--fg-primary)', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{it.label}</span>
                    {it.sub && <span style={{ display:'block', fontFamily:'var(--font-body)', fontSize:10.5, color:'var(--warm-stone)', marginTop:1, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>{it.sub}</span>}
                  </span>
                  {on && <kbd style={{ fontFamily:'var(--font-mono)', fontSize:10, color:'var(--warm-stone)', border:'1px solid var(--soft-line)', borderRadius:5, padding:'1px 6px' }}>↵</kbd>}
                </div>
              </React.Fragment>
            );
          })}
        </div>
        <div style={{ display:'flex', alignItems:'center', gap:14, padding:'9px 16px', borderTop:'1px solid var(--soft-line)', background:'var(--bg-app)', fontFamily:'var(--font-body)', fontSize:10.5, color:'var(--warm-stone)' }}>
          <span><kbd style={{ fontFamily:'var(--font-mono)' }}>↑↓</kbd> navigate</span>
          <span><kbd style={{ fontFamily:'var(--font-mono)' }}>↵</kbd> open</span>
          <span><kbd style={{ fontFamily:'var(--font-mono)' }}>esc</kbd> close</span>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { NAV, Sidebar, Topbar, SessionMenu, NotificationsMenu, ToastHost, ComingSoon, OrdersStub, InsightsStub, SettingsStub, CommandPalette });
