// App shell — bottom tab nav, Tweaks, routing, groups, language, onboarding, notif

// Capture a shareable invite token from the URL as early as possible (before
// auth resolves), so a logged-out visitor can register and still land in the
// invited group. The token is consumed after sign-in (fbAcceptPendingInviteLink).
(function captureInviteToken() {
  try {
    const p = new URLSearchParams(window.location.search);
    const tok = p.get('invite');
    if (tok) {
      localStorage.setItem('zocker_pending_invite', tok);
      window.__INVITE_TOKEN = tok;
      // Strip the param so it isn't re-processed on later navigations.
      p.delete('invite');
      const qs = p.toString();
      window.history.replaceState({}, '', window.location.pathname + (qs ? '?' + qs : ''));
    }
  } catch (_) { /* ignore */ }
})();

function TabBar({ active, onChange, fullBleed = false }) {
  const t = window.t;
  const tabs = [
    { id: 'heute',    label: t('tab_heute'),    icon: <IconHome /> },
    { id: 'kalender', label: t('tab_kalender'), icon: <IconGrid /> },
    { id: 'spiele',   label: t('tab_spiele'),   icon: <IconGame /> },
    { id: 'crew',     label: t('tab_crew'),     icon: <IconUsers /> },
  ];
  return (
    <div className={'tabbar' + (fullBleed ? ' tabbar--safe' : '')}>
      <div className="tabbar__inner">
        {tabs.map(tab => {
          const isActive = active === tab.id;
          return (
            <button key={tab.id} onClick={() => onChange(tab.id)}
              className={'tabbar__btn' + (isActive ? ' tabbar__btn--active' : '')}>
              {tab.icon}
              <span className="tabbar__label">{tab.label}</span>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function IconHome() {
  return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 12l9-8 9 8M5 10v10h14V10"/></svg>;
}
function IconGrid() {
  return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="4" width="18" height="17" rx="2"/><path d="M3 9h18M8 4v17M14 4v17"/></svg>;
}
function IconGame() {
  return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="7" width="20" height="11" rx="4"/><path d="M7 12h3M8.5 10.5v3M15 12h.01M18 12h.01"/></svg>;
}
function IconUsers() {
  return <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="9" cy="8" r="3"/><path d="M3 20c0-3 3-5 6-5s6 2 6 5"/><circle cx="17" cy="9" r="2.5"/><path d="M16 20c0-2 2-4 5-4"/></svg>;
}

function AppLoadingScreen() {
  return (
    <div style={{
      minHeight: '100vh',
      background: 'radial-gradient(ellipse at top, oklch(0.18 0.04 285) 0%, oklch(0.12 0.025 285) 70%)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      fontFamily: "'Space Grotesk', system-ui, sans-serif",
    }}>
      <div style={{ textAlign: 'center' }}>
        <div style={{ fontSize: 36, marginBottom: 16 }}>🎮</div>
        <div style={{ fontSize: 14, color: 'oklch(0.68 0.022 285)', letterSpacing: 0.5 }}>Laden…</div>
      </div>
    </div>
  );
}

// Auth gate — owns Firebase auth state; renders loading / auth / main app.
// Kept separate so MainApp's hooks always run unconditionally (Rules of Hooks).
function App() {
  const [firebaseUser, setFirebaseUser] = React.useState(window.fbAuth ? undefined : null);
  const [fbDataLoaded, setFbDataLoaded] = React.useState(!window.fbAuth);
  const [loadedGroupGames, setLoadedGroupGames] = React.useState(null);
  const [joinedGroupId, setJoinedGroupId] = React.useState(null);
  const [inviteBanner, setInviteBanner] = React.useState(null);

  // If the visitor arrived via an invite link, fetch the group name to show on
  // the auth screen ("you were invited to …").
  React.useEffect(() => {
    if (!window.__INVITE_TOKEN || !window.fbFetchInvite) return;
    let cancelled = false;
    window.fbFetchInvite(window.__INVITE_TOKEN).then(inv => {
      if (!cancelled && inv && inv.groupName) setInviteBanner({ groupName: inv.groupName });
    });
    return () => { cancelled = true; };
  }, []);

  React.useEffect(() => {
    if (!window.fbAuth) return;
    return window.fbAuth.onAuthStateChanged(async (u) => {
      if (u) {
        window.__FB_UID = u.uid;
        const displayName = u.displayName || u.email || 'Zocker';
        window.fbSaveProfile({ displayName, email: (u.email || '').toLowerCase() });
        const me = window.FRIENDS && window.FRIENDS.find(f => f.id === 'me');
        if (me) me.name = displayName.split(' ')[0];
        await window.fbAcceptPendingInvites(u);
        const joined = await window.fbAcceptPendingInviteLink?.(u);
        if (joined?.groupId) setJoinedGroupId(joined.groupId);
        const result = await window.loadUserData(u.uid);
        setLoadedGroupGames(result?.groupGames || null);
        setFirebaseUser(u);
        setFbDataLoaded(true);
      } else {
        window.__FB_UID = null;
        setFirebaseUser(null);
        setFbDataLoaded(false);
        setLoadedGroupGames(null);
      }
    });
  }, []);

  if (firebaseUser === undefined || (firebaseUser && !fbDataLoaded)) return <AppLoadingScreen />;
  if (firebaseUser === null) return <window.AuthScreen inviteBanner={inviteBanner} />;

  // key forces a fresh mount (and fresh data) per signed-in user
  return <MainApp key={firebaseUser.uid} initialGroupGames={loadedGroupGames}
    initialGroupId={joinedGroupId} initialTab={joinedGroupId ? 'crew' : 'heute'} />;
}

function MainApp({ initialGroupGames, initialGroupId, initialTab }) {
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "palette": ["#aaff66", "#b388ff", "#ff5e8a"],
    "theme": "dark",
    "lang": "de",
    "deviceFrame": false,
    "showOnboarding": false,
    "showNotif": false
  }/*EDITMODE-END*/;
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Active UI language. Initialised from the user's saved choice / browser /
  // the app default (see detectInitialLang), then kept in sync via the
  // 'zocker-lang-changed' event that setLang fires — so switching language
  // anywhere re-renders the whole app. window.__LANG mirrors it for the many
  // non-React helpers (t(), day labels, group names, …) that read it directly.
  const [lang, setLang] = React.useState(() => window.detectInitialLang(t.lang));
  window.__LANG = lang;
  React.useEffect(() => {
    const onLangChange = (e) => setLang(e.detail);
    window.addEventListener('zocker-lang-changed', onLangChange);
    return () => window.removeEventListener('zocker-lang-changed', onLangChange);
  }, []);

  const [tab, setTab] = React.useState(initialTab || 'heute');
  const [sheet, setSheet] = React.useState(null);
  const [activeSession, setActiveSession] = React.useState(null);
  const [activeGroupId, setActiveGroupId] = React.useState(initialGroupId || 'all');
  const [dataVersion, bumpData] = React.useReducer(x => x + 1, 0);
  const [notifVisible, setNotifVisible] = React.useState(false);
  const [notifData, setNotifData] = React.useState(null);
  const [addFriendGroup, setAddFriendGroup] = React.useState(null);
  const [, bumpNotifSeen] = React.useReducer(x => x + 1, 0);
  const [steamBusy, setSteamBusy] = React.useState(false);
  // null | { kind: 'private' | 'error', steamId?, message? }
  const [steamStatus, setSteamStatus] = React.useState(null);

  // Open the invite sheet, optionally with a group pre-selected (e.g. when
  // invoked from a Crew/group page). Ignores 'all' and non-string args (events).
  const openAddFriend = React.useCallback((gid) => {
    setAddFriendGroup(typeof gid === 'string' && gid !== 'all' ? gid : null);
    setSheet('addFriend');
  }, []);

  const notifUnread = window.notifUnreadCount ? window.notifUnreadCount() : 0;
  const [groupGames, setGroupGames] = React.useState(() => {
    if (initialGroupGames) return initialGroupGames;
    return Object.fromEntries(
      Object.entries(window.GROUP_GAMES_INITIAL).map(([k, v]) => [k, [...v]])
    );
  });

  // If Steam is already connected, refresh the owned-games library in the
  // background (no OpenID round-trip). New games surface via the
  // 'zocker-games-updated' event the merge fires — no spinner needed since the
  // stored library is shown immediately.
  React.useEffect(() => {
    if (window.__MY_STEAM?.connected && window.fbRefreshSteamLibrary) {
      window.fbRefreshSteamLibrary();
    }
  }, []);

  // Handle Steam OpenID callback (?steam_connected=1&steamid=...)
  React.useEffect(() => {
    const p = new URLSearchParams(window.location.search);
    const clean = () => window.history.replaceState({}, '', window.location.pathname);
    if (p.get('steam_error')) {
      setTab('spiele');
      setSteamStatus({ kind: 'error', message: p.get('steam_error') });
      clean();
      return;
    }
    if (p.get('steam_connected') && p.get('steamid')) {
      setTab('spiele');
      setSteamBusy(true);
      setSteamStatus(null);
      clean();
      window.fbConnectSteam(p.get('steamid')).then((r) => {
        setSteamBusy(false);
        if (r.ok) {
          setSteamStatus(null);
        } else if (r.error === 'private') {
          setSteamStatus({ kind: 'private', steamId: r.steamId });
        } else {
          setSteamStatus({ kind: 'error', message: r.error });
        }
      });
    }
  }, []);

  const toggleGroupGame = React.useCallback((groupId, gameId) => {
    setGroupGames(prev => {
      const cur = prev[groupId] || [];
      const next = cur.includes(gameId) ? cur.filter(x => x !== gameId) : [...cur, gameId];
      const updated = { ...prev, [groupId]: next };
      window.fbSaveGroupGames?.(updated);
      return updated;
    });
  }, []);

  // Apply theme + palette from tweaks
  React.useEffect(() => {
    const root = document.documentElement;
    const isDark = t.theme === 'dark';
    if (isDark) {
      root.style.setProperty('--bg-deep', 'oklch(0.16 0.022 285)');
      root.style.setProperty('--surface', 'oklch(0.215 0.028 285)');
      root.style.setProperty('--surface-2', 'oklch(0.27 0.03 285)');
      root.style.setProperty('--border', 'oklch(0.32 0.025 285)');
      root.style.setProperty('--text', 'oklch(0.96 0.012 285)');
      root.style.setProperty('--muted', 'oklch(0.68 0.022 285)');
    } else {
      root.style.setProperty('--bg-deep', 'oklch(0.985 0.005 285)');
      root.style.setProperty('--surface', 'oklch(1 0 0)');
      root.style.setProperty('--surface-2', 'oklch(0.95 0.008 285)');
      root.style.setProperty('--border', 'oklch(0.9 0.01 285)');
      root.style.setProperty('--text', 'oklch(0.2 0.02 285)');
      root.style.setProperty('--muted', 'oklch(0.55 0.02 285)');
    }
    root.style.setProperty('--accent', t.palette[0]);
    root.style.setProperty('--violet', t.palette[1]);
    root.style.setProperty('--pink', t.palette[2]);
  }, [t.theme, t.palette]);

  // Show a push-style toast (real scheduled reminder, or the demo button).
  const showPushToast = React.useCallback((data) => {
    setNotifData(data || (window.pushSampleToast ? window.pushSampleToast() : null));
    setNotifVisible(true);
    setTimeout(() => setNotifVisible(false), 6000);
  }, []);

  // Let non-app components (ready-check buttons in the session sheet / hero)
  // surface a confirmation toast without prop-drilling.
  React.useEffect(() => { window.zockerToast = showPushToast; }, [showPushToast]);

  // Notif from tweak toggle → show a demo toast
  React.useEffect(() => {
    if (t.showNotif) {
      showPushToast();
      setTweak('showNotif', false);
    }
  }, [t.showNotif]);

  // Schedule the "X min before a game night" push reminder while the app is
  // open. Re-runs when data changes or the push preference is toggled.
  React.useEffect(() => {
    window.scheduleGameNightPushes?.(showPushToast);
    window.checkReadyPrompts?.(showPushToast);
    const onChange = () => window.scheduleGameNightPushes?.(showPushToast);
    window.addEventListener('zocker-push-changed', onChange);
    return () => {
      window.removeEventListener('zocker-push-changed', onChange);
      (window.__pushTimers || []).forEach(clearTimeout);
    };
  }, [dataVersion, lang, showPushToast]);

  // Keep crew data fresh. The app loads everything once at login into window
  // globals; without this it would show that stale snapshot forever. We re-fetch
  // when the tab regains focus / becomes visible and on a light interval while
  // open, then bump dataVersion so every screen re-renders from the new data.
  React.useEffect(() => {
    let last = 0;
    const REFRESH_MS = 30000;       // background poll while the app is open
    const FOCUS_THROTTLE_MS = 8000; // don't hammer on rapid focus toggles
    const refresh = (throttle) => {
      if (document.visibilityState !== 'visible') return;
      const now = Date.now();
      if (throttle && now - last < FOCUS_THROTTLE_MS) return;
      last = now;
      window.refreshUserData?.();
    };
    const onFocus = () => refresh(true);
    const onVisible = () => { if (document.visibilityState === 'visible') refresh(true); };
    const onRefreshed = () => bumpData();
    window.addEventListener('focus', onFocus);
    document.addEventListener('visibilitychange', onVisible);
    window.addEventListener('zocker-data-refreshed', onRefreshed);
    window.addEventListener('zocker-games-updated', onRefreshed);
    const timer = setInterval(() => refresh(false), REFRESH_MS);
    return () => {
      window.removeEventListener('focus', onFocus);
      document.removeEventListener('visibilitychange', onVisible);
      window.removeEventListener('zocker-data-refreshed', onRefreshed);
      window.removeEventListener('zocker-games-updated', onRefreshed);
      clearInterval(timer);
    };
  }, []);

  const wide = window.useWide ? window.useWide() : false;
  const useDesktop = wide && !t.deviceFrame;
  // On real phones / installed PWAs (and not when forcing the iPhone mockup),
  // run the app edge-to-edge so it fills the device like a native app.
  const nativeShell = window.useNativeShell ? window.useNativeShell() : false;
  const fullBleed = !useDesktop && nativeShell && !t.deviceFrame;

  const renderContent = (isWide) => {
    if (tab === 'heute') return <window.ScreenHeute wide={isWide}
      activeGroupId={activeGroupId}
      onChangeGroup={setActiveGroupId}
      onOpenWeekly={() => setSheet('weekly')}
      onOpenDates={() => setSheet('dates')}
      onOpenSession={setActiveSession}
      onOpenNotif={() => setSheet('notifications')}
      notifUnread={notifUnread}
      onOpenProfile={() => setSheet('profile')}
      onOpenAddFriend={openAddFriend}
    />;
    if (tab === 'kalender') return <window.ScreenKalender wide={isWide}
      activeGroupId={activeGroupId}
      onChangeGroup={setActiveGroupId}
      onOpenSession={setActiveSession}
    />;
    if (tab === 'spiele') return <window.ScreenSpiele wide={isWide}
      activeGroupId={activeGroupId}
      groupGames={groupGames}
      onToggleGroupGame={toggleGroupGame}
      steamBusy={steamBusy}
      steamStatus={steamStatus}
      onDismissSteamStatus={() => setSteamStatus(null)}
    />;
    return <window.ScreenCrew wide={isWide}
      activeGroupId={activeGroupId}
      onChangeGroup={setActiveGroupId}
      onOpenWeekly={() => setSheet('weekly')}
      onOpenNewGroup={() => setSheet('newGroup')}
      onOpenAddFriend={openAddFriend}
    />;
  };

  const overlays = (
    <>
      <window.NotificationToast
        visible={notifVisible}
        title={notifData?.title}
        body={notifData?.body}
        actions={notifData?.actions}
        onAction={(a) => {
          setNotifVisible(false);
          if (notifData?.session && a?.ready) {
            window.fbSetReady?.(notifData.session, a.ready);
            bumpData();
          }
        }}
        onClose={() => setNotifVisible(false)}
        onOpen={() => {
          setNotifVisible(false);
          const s = notifData?.session || window.getSessions('all')[0];
          if (s) { setTab('heute'); setActiveSession(s); }
          else { setSheet('notifications'); }
        }}
      />
      <window.SheetWochenplan  open={sheet === 'weekly'}     onClose={() => setSheet(null)} />
      <window.SheetTermine     open={sheet === 'dates'}      onClose={() => setSheet(null)} />
      <window.SheetSession     session={activeSession}       onClose={() => setActiveSession(null)} />
      <window.SheetNotifications open={sheet === 'notifications'}
        onClose={() => { setSheet(null); bumpNotifSeen(); }}
        onOpenSession={setActiveSession} />
      <window.SheetAddFriend   open={sheet === 'addFriend'}  onClose={() => setSheet(null)}
        initialGroupId={addFriendGroup} />
      <window.SheetNewGroup    open={sheet === 'newGroup'}   onClose={() => setSheet(null)}
        onCreated={(g) => g && setActiveGroupId(g.id)} />
      <window.SheetProfile     open={sheet === 'profile'}    onClose={() => setSheet(null)}
        onOpenAddFriend={openAddFriend}
        onGroupsChanged={() => { setActiveGroupId('all'); bumpData(); }} />
      {t.showOnboarding && <window.Onboarding
        onFinish={() => setTweak('showOnboarding', false)}
        onOpenWeekly={() => setSheet('weekly')}
      />}
    </>
  );

  const mobileScreen = (
    <div style={{
      width: '100%', height: '100%',
      background: 'var(--bg-deep)',
      color: 'var(--text)',
      position: 'relative', overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
    }}>
      {/* Top inset: clears the OS status bar when full-bleed, else mimics the
          status-bar gap inside the device frame. */}
      <div style={{ height: fullBleed ? 'max(50px, env(safe-area-inset-top))' : 50, flexShrink: 0 }} />
      <div key={dataVersion} style={{
        flex: 1, overflowY: 'auto', overflowX: 'hidden',
        // Stop rubber-band overscroll from chaining to the document.
        overscrollBehavior: 'contain', WebkitOverflowScrolling: 'touch',
      }}>
        {renderContent(false)}
      </div>
      <TabBar active={tab} onChange={setTab} fullBleed={fullBleed} />
      {overlays}
    </div>
  );

  return (
    <>
      {useDesktop ? (
        <DesktopShell
          tab={tab}
          onChangeTab={setTab}
          activeGroupId={activeGroupId}
          onChangeGroup={setActiveGroupId}
          onOpenProfile={() => setSheet('profile')}
          onOpenAddFriend={openAddFriend}
          onOpenNotif={() => setSheet('notifications')}
          notifUnread={notifUnread}
          content={<div key={dataVersion}>{renderContent(true)}</div>}
          overlays={overlays}
        />
      ) : fullBleed ? (
        // Native, edge-to-edge: the app owns the whole device viewport. Safe-area
        // insets are handled inside mobileScreen (top spacer + tab bar).
        <div style={{
          width: '100vw', height: '100dvh', overflow: 'hidden',
          background: 'var(--bg-deep)',
        }}>
          {mobileScreen}
        </div>
      ) : (
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          gap: 20, height: '100dvh', padding: 20,
          // Keep the app (and its bottom tab bar) clear of the device's
          // home indicator / on-screen controls and the browser chrome.
          paddingTop: 'max(20px, env(safe-area-inset-top))',
          paddingBottom: 'max(20px, env(safe-area-inset-bottom))',
          paddingLeft: 'max(20px, env(safe-area-inset-left))',
          paddingRight: 'max(20px, env(safe-area-inset-right))',
          background: 'radial-gradient(ellipse at top, oklch(0.18 0.04 285) 0%, oklch(0.12 0.025 285) 70%)',
          boxSizing: 'border-box', overflow: 'hidden',
        }}>
          {t.deviceFrame ? (
            <window.IOSDevice dark={t.theme === 'dark'} time="20:14">
              {mobileScreen}
            </window.IOSDevice>
          ) : (
            <PhoneFrame dark={t.theme === 'dark'}>
              {mobileScreen}
            </PhoneFrame>
          )}
        </div>
      )}

      <window.TweaksPanel>
        <window.TweakSection label="Language" />
        <window.TweakRadio
          label="Sprache / Language"
          value={window.__LANG}
          options={window.availableLanguages().map(l => ({ label: l.code.toUpperCase(), value: l.code }))}
          onChange={v => { setTweak('lang', v); window.setLang(v); }}
        />
        <window.TweakSection label="Look" />
        <window.TweakRadio
          label="Theme"
          value={t.theme}
          options={[{ label: 'Dark', value: 'dark' }, { label: 'Light', value: 'light' }]}
          onChange={v => setTweak('theme', v)}
        />
        <window.TweakColor
          label="Palette"
          value={t.palette}
          options={[
            ['#aaff66', '#b388ff', '#ff5e8a'],
            ['#00e0d6', '#7a8cff', '#ff8a5b'],
            ['#ffb74d', '#9c7bff', '#ff5577'],
            ['#7ad94a', '#3ea1ff', '#ff4d6d'],
            ['#ff5e8a', '#b388ff', '#aaff66'],
          ]}
          onChange={v => setTweak('palette', v)}
        />
        <window.TweakToggle
          label="iPhone frame"
          value={t.deviceFrame}
          onChange={v => setTweak('deviceFrame', v)}
        />

        <window.TweakSection label="Account" />
        <window.TweakButton label="Freund einladen" onClick={() => openAddFriend()} />
        <window.TweakButton label="Abmelden" onClick={() => window.fbAuth?.signOut()} />

        <window.TweakSection label="Demo" />
        <window.TweakButton label="Onboarding zeigen" onClick={() => setTweak('showOnboarding', true)} />
        <window.TweakButton label="Push-Notification" onClick={() => showPushToast()} />

        <window.TweakSection label="Navigation" />
        <window.TweakSelect
          label="Gruppe"
          value={activeGroupId}
          options={[
            { label: 'Alle Gruppen', value: 'all' },
            ...window.GROUPS.map(g => ({ label: window.groupName(g), value: g.id })),
          ]}
          onChange={setActiveGroupId}
        />
        <window.TweakSelect
          label="Tab"
          value={tab}
          options={[
            { label: 'Heute', value: 'heute' },
            { label: 'Kalender', value: 'kalender' },
            { label: 'Spiele', value: 'spiele' },
            { label: 'Crew', value: 'crew' },
          ]}
          onChange={setTab}
        />
        <window.TweakButton label="Wochenplan öffnen" onClick={() => setSheet('weekly')} />
        <window.TweakButton label="Freie Termine öffnen" onClick={() => setSheet('dates')} />
        <window.TweakButton label="Session-Detail öffnen" onClick={() => setActiveSession(window.getSessions('all')[0])} />
      </window.TweaksPanel>
    </>
  );
}

// Generic mobile frame (no platform chrome) — rounded rect, subtle bezel
function PhoneFrame({ children, dark }) {
  return (
    <div style={{
      width: 'min(402px, 100%)',
      height: 'min(874px, 100%)',
      borderRadius: 44, overflow: 'hidden',
      position: 'relative',
      background: 'var(--bg-deep)',
      boxShadow: '0 40px 80px rgba(0,0,0,0.45), 0 0 0 1px color-mix(in oklab, var(--border) 60%, transparent), 0 0 0 8px oklch(0.12 0.02 285)',
    }}>
      {children}
    </div>
  );
}

// ── Desktop / tablet shell ───────────────────────────────────────────────────
// Persistent left sidebar (logo, nav, group filter, account) + wide content area.
function DesktopShell({ tab, onChangeTab, activeGroupId, onChangeGroup, onOpenProfile, onOpenAddFriend, onOpenNotif, notifUnread = 0, content, overlays }) {
  const t = window.t;
  const me = window.FRIENDS_BY_ID?.['me'] || { name: 'Zocker' };
  const navItems = [
    { id: 'heute',    label: t('tab_heute'),    icon: <IconHome /> },
    { id: 'kalender', label: t('tab_kalender'), icon: <IconGrid /> },
    { id: 'spiele',   label: t('tab_spiele'),   icon: <IconGame /> },
    { id: 'crew',     label: t('tab_crew'),     icon: <IconUsers /> },
  ];
  // Crew-priority order so the most important crew is on top in the sidebar too.
  const groups = window.groupsByPriority ? window.groupsByPriority() : (window.GROUPS || []);

  return (
    <div style={{
      display: 'flex', height: '100vh', width: '100%',
      background: 'var(--bg-deep)', color: 'var(--text)',
      position: 'relative', overflow: 'hidden',
      fontFamily: "'Space Grotesk', system-ui, sans-serif",
    }}>
      {/* Sidebar */}
      <aside style={{
        width: 280, flexShrink: 0, height: '100%',
        borderRight: '1px solid var(--border)',
        background: 'color-mix(in oklab, var(--surface) 40%, var(--bg-deep))',
        display: 'flex', flexDirection: 'column',
        padding: '22px 16px 18px',
      }}>
        {/* Logo */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '0 8px 22px' }}>
          <div style={{
            width: 40, height: 40, borderRadius: 12,
            background: 'linear-gradient(135deg, var(--accent), color-mix(in oklab, var(--accent) 55%, #000))',
            color: 'var(--bg-deep)', fontWeight: 800, fontSize: 22,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>Z</div>
          <div style={{ fontSize: 17, fontWeight: 700, letterSpacing: -0.4 }}>Zocker</div>
        </div>

        {/* Nav */}
        <nav style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
          {navItems.map(item => {
            const active = tab === item.id;
            return (
              <button key={item.id} onClick={() => onChangeTab(item.id)} style={{
                appearance: 'none', cursor: 'pointer', textAlign: 'left',
                display: 'flex', alignItems: 'center', gap: 12,
                padding: '11px 12px', borderRadius: 12, border: 0,
                background: active ? 'color-mix(in oklab, var(--accent) 14%, transparent)' : 'transparent',
                color: active ? 'var(--accent)' : 'var(--muted)',
                fontFamily: 'inherit', fontSize: 14, fontWeight: 600,
                transition: 'all 160ms ease',
              }}>
                {item.icon}
                <span>{item.label}</span>
              </button>
            );
          })}
        </nav>

        {/* Group filter */}
        <div style={{ marginTop: 22, flex: 1, minHeight: 0, overflowY: 'auto' }}>
          <div style={{
            fontSize: 11, fontWeight: 600, letterSpacing: 0.8, textTransform: 'uppercase',
            color: 'var(--muted)', padding: '0 12px 8px',
          }}>{t('gruppen')}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
            <SidebarGroup active={activeGroupId === 'all'} glyph="◎" hue={145}
              label={t('alle_gruppen')} onClick={() => onChangeGroup('all')} />
            {groups.map(g => (
              <SidebarGroup key={g.id} active={activeGroupId === g.id} glyph={g.glyph} hue={g.hue}
                label={window.groupName(g)}
                onClick={() => onChangeGroup(activeGroupId === g.id ? 'all' : g.id)} />
            ))}
          </div>
        </div>

        {/* Invite + account */}
        <button onClick={onOpenAddFriend} style={{
          appearance: 'none', cursor: 'pointer', width: '100%',
          margin: '12px 0', padding: '11px 12px', borderRadius: 12,
          background: 'color-mix(in oklab, var(--accent) 12%, var(--surface))',
          border: '1px solid color-mix(in oklab, var(--accent) 35%, var(--border))',
          color: 'var(--accent)', fontFamily: 'inherit', fontSize: 13, fontWeight: 700,
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
        }}>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M19 8v6M22 11h-6" />
          </svg>
          {t('freund_einladen')}
        </button>
        <button onClick={onOpenProfile} style={{
          appearance: 'none', cursor: 'pointer', width: '100%',
          padding: 10, borderRadius: 14,
          background: 'var(--surface)', border: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', gap: 12, textAlign: 'left',
        }}>
          <Avatar id="me" size={36} ring />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{
              fontFamily: 'inherit', fontSize: 14, fontWeight: 600, color: 'var(--text)',
              overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            }}>{me.name}</div>
            <div style={{ fontSize: 11, color: 'var(--muted)' }}>{t('profile')}</div>
          </div>
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6"/></svg>
        </button>
      </aside>

      {/* Main */}
      <main style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        {/* Top bar */}
        <div style={{
          height: 60, flexShrink: 0, borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'center', justifyContent: 'flex-end',
          padding: '0 32px', gap: 12,
        }}>
          <button onClick={onOpenNotif} style={{
            appearance: 'none', cursor: 'pointer',
            width: 38, height: 38, borderRadius: 999,
            background: 'var(--surface)', border: '1px solid var(--border)',
            color: 'var(--text)', position: 'relative',
            display: 'flex', alignItems: 'center', justifyContent: 'center',
          }}>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/>
            </svg>
            {notifUnread > 0 && (
              <div style={{
                position: 'absolute', top: 7, right: 9, width: 8, height: 8, borderRadius: '50%',
                background: 'var(--accent)', boxShadow: '0 0 6px var(--accent)',
              }} />
            )}
          </button>
        </div>

        {/* Scrollable content */}
        <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden' }}>
          <div style={{ maxWidth: 1180, margin: '0 auto', padding: '24px 32px 48px' }}>
            {content}
          </div>
        </div>
      </main>

      {overlays}
    </div>
  );
}

function SidebarGroup({ active, glyph, hue, label, onClick }) {
  return (
    <button onClick={onClick} style={{
      appearance: 'none', cursor: 'pointer', textAlign: 'left',
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '8px 12px', borderRadius: 10, border: 0,
      background: active ? `oklch(0.85 0.16 ${hue} / 0.12)` : 'transparent',
      color: active ? `oklch(0.85 0.16 ${hue})` : 'var(--muted)',
      fontFamily: "'Space Grotesk', system-ui", fontSize: 13, fontWeight: 600,
      transition: 'all 140ms ease',
    }}>
      <span style={{ fontSize: 14, width: 18, textAlign: 'center' }}>{glyph}</span>
      <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span>
    </button>
  );
}

Object.assign(window, { App, MainApp, PhoneFrame, DesktopShell });
