// PromptOS app — sidebar + topbar shell
const posGlyphPaths = {
  grid: <g><rect x="2" y="2" width="5" height="5" rx="1.2"/><rect x="9" y="2" width="5" height="5" rx="1.2"/><rect x="2" y="9" width="5" height="5" rx="1.2"/><rect x="9" y="9" width="5" height="5" rx="1.2"/></g>,
  book: <g><path d="M 3 2.5 H 12 a 1.5 1.5 0 0 1 1.5 1.5 v 9.5 H 4.5 A 1.5 1.5 0 0 1 3 12 Z"/><path d="M 13.5 11 H 4.5 a 1.5 1.5 0 0 0 0 3"/><path d="M 6 5.5 h 5"/></g>,
  spark: <path d="M 8 1.5 L 9.6 6.4 L 14.5 8 L 9.6 9.6 L 8 14.5 L 6.4 9.6 L 1.5 8 L 6.4 6.4 Z"/>,
  blocks: <g><rect x="2" y="2" width="12" height="4" rx="1.3"/><rect x="2" y="9" width="8" height="4" rx="1.3"/><path d="M 12.5 9.5 v 3 M 11 11 h 3"/></g>,
  bot: <g><rect x="2.5" y="5" width="11" height="8.5" rx="2.5"/><path d="M 8 5 V 2.5 M 6.5 2.5 h 3"/><circle cx="5.8" cy="9" r="0.6" fill="currentColor"/><circle cx="10.2" cy="9" r="0.6" fill="currentColor"/><path d="M 6 11.3 h 4"/></g>,
  flask: <g><path d="M 6 2 V 6.5 L 2.7 12.3 A 1.3 1.3 0 0 0 3.8 14 H 12.2 A 1.3 1.3 0 0 0 13.3 12.3 L 10 6.5 V 2"/><path d="M 5 2 h 6"/><path d="M 4.2 10.5 h 7.6"/></g>,
  store: <g><path d="M 2.5 6 L 3.5 2.5 H 12.5 L 13.5 6 a 1.8 1.8 0 1 1 -3.6 0 a 1.8 1.8 0 1 1 -3.7 0 a 1.8 1.8 0 1 1 -3.7 0 Z"/><path d="M 3.5 8 V 13.5 H 12.5 V 8"/><path d="M 6.5 13.5 V 10.5 h 3 v 3"/></g>,
  graph: <g><circle cx="3.8" cy="8" r="1.9"/><circle cx="12" cy="3.4" r="1.7"/><circle cx="12" cy="12.6" r="1.7"/><path d="M 5.5 7 L 10.4 4.2 M 5.5 9 L 10.4 11.8"/></g>,
  chart: <g><path d="M 2.5 2.5 V 13.5 H 13.5"/><path d="M 5 11 V 8"/><path d="M 8 11 V 5.5"/><path d="M 11 11 V 7"/></g>,
  heart: <path d="M 8 13.5 C 4 10.5 2 8.3 2 5.9 A 3 3 0 0 1 8 4.6 A 3 3 0 0 1 14 5.9 C 14 8.3 12 10.5 8 13.5 Z"/>,
  trash: <g><path d="M 3 4.5 h 10 M 6.5 4.5 V 3 a 1 1 0 0 1 1 -1 h 1 a 1 1 0 0 1 1 1 v 1.5"/><path d="M 4.3 4.5 L 5 13 a 1.2 1.2 0 0 0 1.2 1 h 3.6 A 1.2 1.2 0 0 0 11 13 l 0.7 -8.5"/><path d="M 6.7 7 v 4.5 M 9.3 7 V 11.5"/></g>,
  people: <g><circle cx="5.5" cy="6" r="2.4"/><path d="M 1.5 13 a 4 4 0 0 1 8 0"/><circle cx="11" cy="6.3" r="2"/><path d="M 10.2 9.3 a 4 4 0 0 1 4.3 3.7"/></g>,
};

function NavGlyph({ kind, active }) {
  const c = active ? '#fff' : T.dim;
  return (
    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke={c} strokeWidth="1.4"
      strokeLinecap="round" strokeLinejoin="round"
      style={{ display: 'inline-block', flex: '0 0 auto', color: c }}>
      {posGlyphPaths[kind] || <circle cx="8" cy="8" r="5" />}
    </svg>
  );
}

const posNav = [
  ['dashboard', 'Dashboard', 'grid'],
  ['library', 'Library', 'book'],
  ['generator', 'AI Generator', 'spark'],
  ['builder', 'Prompt Builder', 'blocks'],
  ['agents', 'AI Agents', 'bot'],
  ['lab', 'Testing Lab', 'flask'],
  ['insights', 'Insights', 'chart'],
  ['favorites', 'Favorites', 'heart'],
  ['trash', 'Trash', 'trash'],
];

// Fallback offline saja — koleksi sebenarnya dimuat dari PostgreSQL via /api/state.
const posCollections = [];

function NavItem({ id, label, glyph, route, setRoute }) {
  const active = route === id;
  const [h, setH] = React.useState(false);
  return (
    <div onClick={() => setRoute(id)}
      onMouseEnter={() => setH(true)} onMouseLeave={() => setH(false)}
      style={{
        display: 'flex', alignItems: 'center', gap: 11, padding: '9px 12px', borderRadius: 9,
        cursor: 'pointer', userSelect: 'none',
        background: active ? T.grad : (h ? T.hov : 'transparent'),
        color: active ? '#fff' : (h ? T.text : T.mut),
        fontSize: 13.5, fontWeight: active ? 700 : 500, transition: 'background .15s, color .15s',
      }}>
      <NavGlyph kind={glyph} active={active} />
      <span>{label}</span>
    </div>
  );
}

const posCollectionPalette = ['#f472b6', '#60a5fa', '#f5b94a', '#34d399', '#a78bfa', '#22b8cf', '#f87171'];

function Sidebar({ route, setRoute, cols = posCollections, addCollection, deleteCollection, activeCol, selectCol, colCounts = {}, cap = {}, colLimit = Infinity, onUpgrade }) {
  const [adding, setAdding] = React.useState(false);
  const [name, setName] = React.useState('');
  const [open, setOpen] = React.useState(true);
  const atColLimit = cols.length >= colLimit;
  const submit = () => {
    const n = name.trim();
    if (n && addCollection && !cols.some(([c]) => c === n)) {
      addCollection(n, posCollectionPalette[cols.length % posCollectionPalette.length]);
    }
    setName(''); setAdding(false);
  };
  return (
    <div style={{
      width: 228, flex: '0 0 auto', background: T.panel, borderRight: `1px solid ${T.line}`,
      display: 'flex', flexDirection: 'column', padding: '18px 14px', gap: 3, overflowY: 'auto',
    }}>
      <div style={{ padding: '2px 10px 16px' }}><AppLogo /></div>
      {posNav.map(([id, label, glyph]) => (
        <NavItem key={id} id={id} label={label} glyph={glyph} route={route} setRoute={setRoute} />
      ))}
      {cap.manageUsers && (
        <NavItem id="users" label="User Management" glyph="people" route={route} setRoute={setRoute} />
      )}
      <div style={{
        display: 'flex', alignItems: 'center', margin: '18px 12px 8px', fontSize: 11, fontWeight: 700,
        letterSpacing: '0.1em', color: T.dim, textTransform: 'uppercase', userSelect: 'none',
      }}>
        <span onClick={() => setOpen((o) => !o)} style={{ cursor: 'pointer' }}>Collections</span>
        {cap.edit && <span onClick={() => setRoute('manage')} title="Kelola koleksi & kategori"
          style={{ marginLeft: 'auto', fontSize: 14, color: T.dim, cursor: 'pointer' }}>⚙</span>}
        <span onClick={() => setOpen((o) => !o)} style={{ marginLeft: cap.edit ? 8 : 'auto', fontSize: 10, cursor: 'pointer' }}>{open ? '▾' : '▸'}</span>
      </div>
      {open && cols.map(([name, color, icon]) => {
        const active = activeCol === name;
        return (
          <div key={name} onClick={() => selectCol && selectCol(name)}
            title={active ? 'Klik lagi untuk menampilkan semua prompt' : `Lihat prompt di "${name}"`}
            style={{
              display: 'flex', alignItems: 'center', gap: 11, padding: '8px 12px', borderRadius: 9,
              color: active ? T.text : T.mut, fontSize: 13, cursor: 'pointer', userSelect: 'none',
              background: active ? T.vioSoft : 'transparent', fontWeight: active ? 700 : 500,
              transition: 'background .15s, color .15s',
            }}>
            <span style={{ color, flex: '0 0 auto', display: 'flex' }}>
              {icon ? <IconGlyph name={icon} size={15} color="currentColor" />
                    : <span style={{ width: 9, height: 9, borderRadius: 3, background: color }}></span>}
            </span>
            <span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{name}</span>
            {colCounts[name] ? (
              <span style={{
                marginLeft: 'auto', fontSize: 10.5, fontWeight: 700, flex: '0 0 auto',
                color: active ? T.vio : T.dim,
              }}>{colCounts[name]}</span>
            ) : null}
          </div>
        );
      })}
      {open && adding ? (
        <div style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '4px 12px' }}>
          <span style={{ width: 9, height: 9, borderRadius: 3, background: T.vio, flex: '0 0 auto' }}></span>
          <input autoFocus value={name}
            onChange={(e) => setName(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') submit(); if (e.key === 'Escape') { setName(''); setAdding(false); } }}
            onBlur={submit}
            placeholder="Nama koleksi…"
            style={{
              flex: 1, minWidth: 0, fontFamily: T.font, fontSize: 13, color: T.text,
              background: T.card, border: `1px solid ${T.line2}`, borderRadius: 7,
              padding: '5px 8px', outline: 'none',
            }} />
        </div>
      ) : open && cap.edit && atColLimit ? (
        <div onClick={() => onUpgrade && onUpgrade({ feature: 'Koleksi tak terbatas', plan: 'Pro', desc: `Paket Anda dibatasi ${colLimit} koleksi. Upgrade ke Pro untuk membuat tanpa batas.` })}
          title={`Paket Anda dibatasi ${colLimit} koleksi`}
          style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '8px 12px', color: T.dim, fontSize: 13, cursor: 'pointer' }}>
          <span style={{ width: 9, textAlign: 'center' }}>🔒</span><span>Upgrade untuk koleksi lain</span>
        </div>
      ) : open && cap.edit ? (
        <div onClick={() => setAdding(true)}
          style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '8px 12px', color: T.dim, fontSize: 13, cursor: 'pointer' }}>
          <span style={{ width: 9, textAlign: 'center' }}>+</span><span>New Collection</span>
        </div>
      ) : null}
    </div>
  );
}

function Topbar({ query, setQuery, goLibrary, theme = 'dark', toggleTheme, user, onLogout, onPlans, lib = [], onPick }) {
  const ref = React.useRef(null);
  const boxRef = React.useRef(null);
  const [open, setOpen] = React.useState(false);
  const [hi, setHi] = React.useState(0);
  React.useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); ref.current && ref.current.focus(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);
  React.useEffect(() => {
    const close = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);

  const q = (query || '').trim().toLowerCase();
  const matches = q
    ? lib.filter((p) => (p.t + ' ' + p.d + ' ' + p.m + ' ' + p.cat).toLowerCase().includes(q)).slice(0, 6)
    : [];
  const showDrop = open && q && matches.length > 0;
  React.useEffect(() => { setHi(0); }, [query]);

  const pick = (p) => {
    setOpen(false);
    if (onPick) onPick(p); else goLibrary();
  };
  const onSearchKey = (e) => {
    if (!showDrop) return;
    if (e.key === 'ArrowDown') { e.preventDefault(); setHi((h) => (h + 1) % matches.length); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setHi((h) => (h - 1 + matches.length) % matches.length); }
    else if (e.key === 'Enter') { e.preventDefault(); pick(matches[hi]); }
    else if (e.key === 'Escape') { setOpen(false); }
  };
  return (
    <div style={{
      height: 60, flex: '0 0 auto', borderBottom: `1px solid ${T.line}`, background: T.panel,
      display: 'flex', alignItems: 'center', gap: 16, padding: '0 22px',
    }}>
      <div ref={boxRef} style={{ flex: '0 1 460px', position: 'relative' }}>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 9, background: T.card,
          border: `1px solid ${showDrop ? T.line2 : T.line}`,
          borderRadius: showDrop ? '10px 10px 0 0' : 10, padding: '9px 13px',
        }}>
          <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke={T.dim} strokeWidth="1.6"
            strokeLinecap="round" style={{ flex: '0 0 auto' }}>
            <circle cx="7" cy="7" r="4.6" /><path d="M 10.5 10.5 L 14 14" />
          </svg>
          <input ref={ref} value={query}
            onChange={(e) => { setQuery(e.target.value); setOpen(true); }}
            onFocus={() => setOpen(true)}
            onKeyDown={onSearchKey}
            placeholder="Search prompts, tags, categories…"
            style={{
              flex: 1, background: 'transparent', border: 'none', outline: 'none',
              fontFamily: T.font, fontSize: 13, color: T.text,
            }} />
          {query ? (
            <span onClick={() => { setQuery(''); ref.current && ref.current.focus(); }}
              title="Bersihkan" style={{ color: T.dim, fontSize: 14, cursor: 'pointer', flex: '0 0 auto' }}>×</span>
          ) : (
            <span style={{
              fontSize: 10.5, fontWeight: 600, color: T.dim, border: `1px solid ${T.line2}`,
              borderRadius: 5, padding: '2px 6px', flex: '0 0 auto',
            }}>⌘ K</span>
          )}
        </div>
        {showDrop && (
          <div style={{
            position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 45,
            background: T.card, border: `1px solid ${T.line2}`, borderTop: 'none',
            borderRadius: '0 0 11px 11px', boxShadow: '0 14px 34px rgba(0,0,0,0.4)',
            overflow: 'hidden', padding: 6,
          }}>
            {matches.map((p, i) => (
              <div key={p.id} onMouseDown={(e) => { e.preventDefault(); pick(p); }}
                onMouseEnter={() => setHi(i)}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 8,
                  cursor: 'pointer', background: hi === i ? T.hov : 'transparent',
                }}>
                <MChip name={p.m} size={22} />
                <span style={{ display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0, flex: 1 }}>
                  <span style={{ fontSize: 13, fontWeight: 600, color: T.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.t}</span>
                  <span style={{ fontSize: 11, color: T.dim, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.d}</span>
                </span>
                <span style={{ fontSize: 10, color: T.dim, flex: '0 0 auto', border: `1px solid ${T.line}`, borderRadius: 6, padding: '2px 7px' }}>{p.cat}</span>
              </div>
            ))}
            <div onMouseDown={(e) => { e.preventDefault(); setOpen(false); goLibrary(); }}
              style={{
                padding: '8px 10px', marginTop: 2, borderTop: `1px solid ${T.line}`,
                fontSize: 11.5, color: T.vio, fontWeight: 600, cursor: 'pointer', textAlign: 'center',
              }}>Lihat semua hasil untuk “{query}”</div>
          </div>
        )}
      </div>
      <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 14 }}>
        <span title={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
          onClick={toggleTheme} style={{ display: 'inline-flex', cursor: 'pointer' }}>
          {theme === 'dark' ? (
            <svg width="17" height="17" viewBox="0 0 16 16" fill="none" stroke={T.mut} strokeWidth="1.5"
              strokeLinecap="round" strokeLinejoin="round">
              <path d="M 13.5 9.5 A 5.8 5.8 0 0 1 6.5 2.5 A 5.8 5.8 0 1 0 13.5 9.5 Z" />
            </svg>
          ) : (
            <svg width="17" height="17" viewBox="0 0 16 16" fill="none" stroke={T.mut} strokeWidth="1.5"
              strokeLinecap="round" strokeLinejoin="round">
              <circle cx="8" cy="8" r="3.4" />
              <path d="M 8 1 V 2.6 M 8 13.4 V 15 M 1 8 H 2.6 M 13.4 8 H 15 M 3 3 L 4.2 4.2 M 11.8 11.8 L 13 13 M 13 3 L 11.8 4.2 M 4.2 11.8 L 3 13" />
            </svg>
          )}
        </span>
        <NotificationBell lib={lib} onPick={onPick} />
        <UserMenu user={user} onLogout={onLogout} onPlans={onPlans} />
      </div>
    </div>
  );
}

// notifikasi nyata: diturunkan dari prompt yang baru dibuat (created_at), status baca disimpan di localStorage
function NotificationBell({ lib = [], onPick }) {
  const [open, setOpen] = React.useState(false);
  const [read, setRead] = React.useState(() => {
    try { return new Set(JSON.parse(localStorage.getItem('pos.notifRead') || '[]')); } catch { return new Set(); }
  });
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);
  const persist = (s) => { setRead(new Set(s)); try { localStorage.setItem('pos.notifRead', JSON.stringify([...s])); } catch {} };

  const ago = (d) => {
    const s = Math.max(0, (Date.now() - d.getTime()) / 1000);
    if (s < 60) return 'baru saja';
    if (s < 3600) return Math.floor(s / 60) + ' mnt lalu';
    if (s < 86400) return Math.floor(s / 3600) + ' jam lalu';
    if (s < 604800) return Math.floor(s / 86400) + ' hari lalu';
    return Math.floor(s / 604800) + ' mgg lalu';
  };
  // ambil prompt yang dibuat dalam 30 hari terakhir, terbaru dulu, maksimal 15
  const notifs = lib
    .filter((p) => p.created && !isNaN(new Date(p.created)))
    .map((p) => ({ id: p.id, title: p.t || '(tanpa judul)', cat: p.cat || '', m: p.m, when: new Date(p.created) }))
    .filter((n) => (Date.now() - n.when.getTime()) < 30 * 86400000)
    .sort((a, b) => b.when - a.when)
    .slice(0, 15);
  const unread = notifs.filter((n) => !read.has(n.id)).length;

  const markAll = () => persist(new Set(notifs.map((n) => n.id)));
  const onItem = (n) => {
    const s = new Set(read); s.add(n.id); persist(s);
    setOpen(false);
    onPick && onPick({ id: n.id });
  };

  return (
    <div ref={ref} style={{ position: 'relative', display: 'inline-flex' }}>
      <span title="Notifikasi" onClick={() => setOpen((o) => !o)}
        style={{ display: 'inline-flex', position: 'relative', cursor: 'pointer' }}>
        <svg width="17" height="17" viewBox="0 0 16 16" fill="none" stroke={open ? T.vio : T.mut} strokeWidth="1.5"
          strokeLinecap="round" strokeLinejoin="round">
          <path d="M 3 11.5 c 1 -1 1.3 -2.6 1.3 -4.3 a 3.7 3.7 0 0 1 7.4 0 c 0 1.7 0.3 3.3 1.3 4.3 Z" />
          <path d="M 6.8 13.5 a 1.3 1.3 0 0 0 2.4 0" />
        </svg>
        {unread > 0 && (
          <span style={{
            position: 'absolute', right: -5, top: -5, minWidth: 14, height: 14, padding: '0 3px',
            borderRadius: 7, background: '#ef4444', color: '#fff', fontSize: 9, fontWeight: 800,
            display: 'flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1,
          }}>{unread > 9 ? '9+' : unread}</span>
        )}
      </span>
      {open && (
        <div style={{
          position: 'absolute', right: 0, top: 30, width: 320, zIndex: 50,
          background: T.card, border: `1px solid ${T.line2}`, borderRadius: 12,
          boxShadow: '0 12px 34px rgba(0,0,0,0.45)', overflow: 'hidden',
        }}>
          <div style={{
            display: 'flex', alignItems: 'center', justifyContent: 'space-between',
            padding: '11px 13px', borderBottom: `1px solid ${T.line}`,
          }}>
            <span style={{ fontSize: 13, fontWeight: 700, color: T.text }}>Notifikasi {unread > 0 && (
              <span style={{ fontSize: 10.5, color: T.dim, fontWeight: 600 }}>· {unread} baru</span>
            )}</span>
            {notifs.length > 0 && unread > 0 && (
              <span onClick={markAll} style={{ fontSize: 11, color: T.vio, fontWeight: 600, cursor: 'pointer' }}>Tandai dibaca</span>
            )}
          </div>
          <div style={{ maxHeight: 360, overflowY: 'auto' }}>
            {notifs.length === 0 ? (
              <div style={{ padding: '26px 14px', textAlign: 'center', fontSize: 12, color: T.dim }}>Belum ada notifikasi</div>
            ) : notifs.map((n) => {
              const isRead = read.has(n.id);
              return (
                <div key={n.id} onClick={() => onItem(n)}
                  style={{
                    display: 'flex', alignItems: 'flex-start', gap: 10, padding: '10px 13px',
                    cursor: 'pointer', borderBottom: `1px solid ${T.line}`,
                    background: isRead ? 'transparent' : T.hov,
                  }}
                  onMouseEnter={(e) => (e.currentTarget.style.background = T.hov)}
                  onMouseLeave={(e) => (e.currentTarget.style.background = isRead ? 'transparent' : T.hov)}>
                  <span style={{ flex: '0 0 auto', marginTop: 1 }}><MChip name={n.m} size={26} /></span>
                  <span style={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 0, flex: 1 }}>
                    <span style={{ fontSize: 12.5, color: T.text, lineHeight: 1.35 }}>
                      Prompt baru <b>{n.title}</b>{n.cat ? <span style={{ color: T.dim }}> di {n.cat}</span> : null}
                    </span>
                    <span style={{ fontSize: 10.5, color: T.dim }}>{ago(n.when)}</span>
                  </span>
                  {!isRead && <span style={{ flex: '0 0 auto', width: 7, height: 7, borderRadius: 4, background: T.vio, marginTop: 5 }}></span>}
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

function UserMenu({ user = { username: 'User', role: 'viewer' }, onLogout, onPlans }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const close = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, []);
  const rc = (posRoleMeta[user.role] || { c: T.vio }).c;
  const plan = (typeof posPlanMap !== 'undefined' && posPlanMap[user.plan]) || { name: 'Basic', color: '#60a5fa' };
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <span onClick={() => setOpen((o) => !o)} style={{ display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer' }}>
        <span style={{
          width: 30, height: 30, borderRadius: 15,
          background: `linear-gradient(135deg, ${rc}, ${T.vio})`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          color: '#fff', fontSize: 12, fontWeight: 800,
        }}>{user.username[0].toUpperCase()}</span>
        <span style={{ display: 'flex', flexDirection: 'column', lineHeight: 1.2 }}>
          <span style={{ fontSize: 12.5, fontWeight: 700, color: T.text }}>{user.username}</span>
          <span style={{ fontSize: 10.5, color: plan.color, fontWeight: 600 }}>{plan.name} plan</span>
        </span>
        <span style={{ fontSize: 10, color: T.dim }}>▾</span>
      </span>
      {open && (
        <div style={{
          position: 'absolute', right: 0, top: 42, width: 220, zIndex: 40,
          background: T.card, border: `1px solid ${T.line2}`, borderRadius: 12,
          boxShadow: '0 12px 34px rgba(0,0,0,0.45)', padding: 8,
        }}>
          <div style={{ padding: '8px 10px 10px', borderBottom: `1px solid ${T.line}`, marginBottom: 6 }}>
            <div style={{ fontSize: 13, fontWeight: 700, color: T.text }}>{user.username}</div>
            <div style={{ marginTop: 5, display: 'flex', gap: 6 }}>
              <RoleBadge role={user.role} />
              <Badge color={plan.color}>{plan.name}</Badge>
            </div>
          </div>
          {onPlans && (
            <div onClick={() => { setOpen(false); onPlans(); }} style={{
              display: 'flex', alignItems: 'center', gap: 9, padding: '9px 10px', borderRadius: 8,
              cursor: 'pointer', fontSize: 13, fontWeight: 600, color: T.text,
            }}
              onMouseEnter={(e) => (e.currentTarget.style.background = T.hov)}
              onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
              <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                <path d="M2 5.5 L8 2 L14 5.5 L8 9 Z" /><path d="M2 10.5 L8 14 L14 10.5" />
              </svg>
              Kelola paket
            </div>
          )}
          <div onClick={onLogout} style={{
            display: 'flex', alignItems: 'center', gap: 9, padding: '9px 10px', borderRadius: 8,
            cursor: 'pointer', fontSize: 13, fontWeight: 600, color: '#f87171',
          }}
            onMouseEnter={(e) => (e.currentTarget.style.background = 'rgba(248,113,113,0.1)')}
            onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
            <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
              <path d="M 6 14 H 3.5 A 1.5 1.5 0 0 1 2 12.5 V 3.5 A 1.5 1.5 0 0 1 3.5 2 H 6" />
              <path d="M 10 11 L 13.5 8 L 10 5 M 13.5 8 H 6" />
            </svg>
            Logout
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { Sidebar, Topbar, UserMenu, posNav, posCollections });
