/* ============================================================
   Main app shell — sidebar, top bar, view routing, player state
   ============================================================ */

const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA, useRef: useRefA, useCallback } = React;

function useIsMobile() {
  const [m, setM] = useStateA(() =>
    typeof window !== "undefined" && window.matchMedia("(max-width: 768px)").matches
  );
  useEffectA(() => {
    const mq = window.matchMedia("(max-width: 768px)");
    const fn = () => setM(mq.matches);
    mq.addEventListener("change", fn);
    return () => mq.removeEventListener("change", fn);
  }, []);
  return m;
}

function App() {
  const isMobile = useIsMobile();

  // --- AUTH (server-driven) ---
  const [me, setMe] = useStateA(null);            // { username, isAdmin } when authed
  const [authChecked, setAuthChecked] = useStateA(false);
  const [libraryLoaded, setLibraryLoaded] = useStateA(false);
  const [drawerOpen, setDrawerOpen] = useStateA(false);

  // Initial session check
  useEffectA(() => {
    (async () => {
      try {
        const res = await fetch("/api/me", { credentials: "include" });
        if (res.ok) setMe(await res.json());
      } catch (e) {}
      setAuthChecked(true);
    })();
  }, []);

  // Load library after auth
  useEffectA(() => {
    if (!me) return;
    (async () => {
      try {
        await window.loadLibrary();
      } catch (e) {
        console.warn("Library load failed:", e);
      }
      setLibraryLoaded(true);
    })();
  }, [me]);

  // --- VIEW STATE ---
  const [view, setView] = useStateA({ kind: "home" });
  const [history, setHistory] = useStateA([]);
  const [forward, setForward] = useStateA([]);
  const [search, setSearch] = useStateA("");

  const nav = useCallback((nextView) => {
    setHistory(h => [...h, view]);
    setForward([]);
    setView(nextView);
    setDrawerOpen(false);
  }, [view]);

  function navBack() {
    if (history.length === 0) return;
    const prev = history[history.length - 1];
    setHistory(h => h.slice(0, -1));
    setForward(f => [view, ...f]);
    setView(prev);
  }
  function navFwd() {
    if (forward.length === 0) return;
    const next = forward[0];
    setForward(f => f.slice(1));
    setHistory(h => [...h, view]);
    setView(next);
  }

  // --- PLAYER STATE ---
  const [queue, setQueue] = useStateA([]);
  const [queueIdx, setQueueIdx] = useStateA(0);
  const [playing, setPlaying] = useStateA(false);
  const [position, setPosition] = useStateA(0);
  const [volume, setVolume] = useStateA(100);
  const [shuffle, setShuffle] = useStateA(false);
  const [repeat, setRepeat] = useStateA("off");
  // Quality: default is HIGH every session. If the user upgrades to MASTER, we
  // remember that for the *current browser session* only (sessionStorage), so
  // next time the app loads they're back on HIGH unless they upgrade again.
  const [quality, setQuality] = useStateA(() => {
    try { return sessionStorage.getItem("disco-quality") || "high"; } catch (e) { return "high"; }
  });
  const [fullscreen, setFullscreen] = useStateA(false);
  const [queueOpen, setQueueOpen] = useStateA(false);
  const [qualityMenu, setQualityMenu] = useStateA(false);
  const [showLyrics, setShowLyrics] = useStateA(false);
  const [share, setShare] = useStateA(null); // { kind, id, label }

  // Persist quality to sessionStorage (per-tab/session only) + broadcast to
  // MetaStore so quality pills re-render.
  useEffectA(() => {
    try { sessionStorage.setItem("disco-quality", quality); } catch (e) {}
    window.MetaStore?.setActiveQuality?.(quality);
  }, [quality]);

  const [likes, setLikes] = useStateA(() => {
    try {
      const s = JSON.parse(localStorage.getItem("disco-likes") || "[]");
      return new Set(s);
    } catch (e) { return new Set(); }
  });

  const [recentTracks, setRecentTracks] = useStateA(() => {
    try {
      const s = JSON.parse(localStorage.getItem("disco-recent") || "null");
      return s || [];
    } catch (e) { return []; }
  });

  const [playCounts, setPlayCounts] = useStateA(() => {
    try { return JSON.parse(localStorage.getItem("disco-plays") || "{}"); }
    catch (e) { return {}; }
  });
  const playedThisSessionRef = useRefA(new Set());

  const trackById = useCallback((id) => {
    if (!id) return null;
    for (const a of window.MUSIC_DB.all) {
      for (const t of a.tracks) {
        if (t.id === id) return t;
      }
    }
    return null;
  }, [libraryLoaded]);
  const albumOf = useCallback((track) => {
    if (!track) return null;
    return window.MUSIC_DB.all.find(a => a.id === track.albumId);
  }, [libraryLoaded]);

  const current = queue[queueIdx] || null;

  // --- PLAYBACK ENGINE ---
  // Direct streaming via <audio> with /api/stream/{trackId}?q={quality}.
  // Same-origin so the session cookie is sent automatically.
  const audioRef = useRefA(null);
  const [extractedMeta, setExtractedMeta] = useStateA({});

  // Build the streaming URL for the current track + quality
  const currentStreamUrl = current
    ? `/api/stream/${encodeURIComponent(current.id)}?q=${quality}`
    : null;

  // Track the previous URL so we can detect real src changes (track or quality
  // switch) and preserve playback position when only the quality changes.
  const lastUrlRef = useRefA(null);
  const preservedPosRef = useRefA(0);

  useEffectA(() => {
    const audio = audioRef.current;
    if (!audio) return;

    if (!current) {
      audio.pause();
      if (audio.src) { audio.removeAttribute("src"); audio.load(); }
      lastUrlRef.current = null;
      return;
    }
    if (lastUrlRef.current !== currentStreamUrl) {
      const isQualitySwap = lastUrlRef.current
        && lastUrlRef.current.split("?")[0] === currentStreamUrl.split("?")[0];
      if (isQualitySwap) preservedPosRef.current = audio.currentTime;
      audio.src = currentStreamUrl;
      audio.load();
      if (isQualitySwap) {
        const seekOnReady = () => {
          audio.currentTime = preservedPosRef.current;
          audio.removeEventListener("loadedmetadata", seekOnReady);
        };
        audio.addEventListener("loadedmetadata", seekOnReady);
      }
      lastUrlRef.current = currentStreamUrl;
    }
    audio.volume = volume / 100;
    if (playing) {
      audio.play().catch(e => {
        console.warn("play() rejected:", e);
        setPlaying(false);
      });
    } else {
      audio.pause();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [current?.id, playing, quality]);

  useEffectA(() => {
    const audio = audioRef.current;
    if (audio) audio.volume = volume / 100;
  }, [volume]);

  useEffectA(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const onTime = () => setPosition(audio.currentTime);
    const onEnded = () => handleTrackEnd();
    const onLoaded = () => {
      if (current && audio.duration && Math.abs(audio.duration - current.d) > 1) {
        current.d = audio.duration;
        setPosition(p => p);
      }
    };
    audio.addEventListener("timeupdate", onTime);
    audio.addEventListener("ended", onEnded);
    audio.addEventListener("loadedmetadata", onLoaded);
    return () => {
      audio.removeEventListener("timeupdate", onTime);
      audio.removeEventListener("ended", onEnded);
      audio.removeEventListener("loadedmetadata", onLoaded);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [current?.id]);

  function handleTrackEnd() {
    if (repeat === "one") { setPosition(0); return; }
    if (queueIdx < queue.length - 1) {
      setQueueIdx(queueIdx + 1);
      setPosition(0);
    } else if (repeat === "all") {
      setQueueIdx(0);
      setPosition(0);
    } else {
      setPlaying(false);
      setPosition(0);
    }
  }

  function togglePlay() { if (current) setPlaying(p => !p); }
  function seek(t) {
    const clamped = Math.max(0, Math.min(current?.d || 0, t));
    setPosition(clamped);
    if (audioRef.current) audioRef.current.currentTime = clamped;
  }
  function startQueue(tracks, startIdx = 0, shuf = false) {
    let q = [...tracks];
    if (shuf) {
      const first = q[startIdx];
      const rest = q.filter((_, i) => i !== startIdx);
      rest.sort(() => Math.random() - 0.5);
      q = [first, ...rest];
      startIdx = 0;
    }
    setQueue(q);
    setQueueIdx(startIdx);
    setPosition(0);
    setPlaying(true);
    playedThisSessionRef.current = new Set();
    const t = q[startIdx];
    if (t) addRecent(t.id);
  }
  function playAlbum(album, shuf = false) { startQueue(album.tracks, 0, shuf || shuffle); }
  function playTrack(track, album) {
    if (!album) album = albumOf(track);
    const idx = album ? album.tracks.findIndex(t => t.id === track.id) : 0;
    startQueue(album ? album.tracks : [track], idx, false);
  }
  function playList(tracks, startIdx = 0) { startQueue(tracks, startIdx, false); }
  function playPlaylist(playlist, shuf = false, startIdx = 0) {
    const tracks = playlist.trackIds.map(id => trackById(id)).filter(Boolean);
    startQueue(tracks, startIdx, shuf || shuffle);
  }
  function next() {
    if (queue.length === 0) return;
    if (queueIdx < queue.length - 1) { setQueueIdx(queueIdx + 1); setPosition(0); }
    else if (repeat === "all") { setQueueIdx(0); setPosition(0); }
  }
  function prev() {
    if (position > 4) { setPosition(0); return; }
    if (queueIdx > 0) { setQueueIdx(queueIdx - 1); setPosition(0); }
  }
  function cycleRepeat() { setRepeat(r => r === "off" ? "all" : r === "all" ? "one" : "off"); }
  function jumpQueue(idx) { setQueueIdx(idx); setPosition(0); setPlaying(true); }

  function toggleLike(id) {
    setLikes(prev => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id); else n.add(id);
      try { localStorage.setItem("disco-likes", JSON.stringify([...n])); } catch (e) {}
      return n;
    });
  }
  const isLiked = (id) => likes.has(id);

  function addRecent(id) {
    setRecentTracks(prev => {
      const filtered = prev.filter(x => x !== id);
      const next = [id, ...filtered].slice(0, 30);
      try { localStorage.setItem("disco-recent", JSON.stringify(next)); } catch (e) {}
      return next;
    });
  }

  function maybeCountPlay(trackId) {
    if (playedThisSessionRef.current.has(trackId)) return;
    playedThisSessionRef.current.add(trackId);
    setPlayCounts(prev => {
      const next = { ...prev, [trackId]: (prev[trackId] || 0) + 1 };
      try { localStorage.setItem("disco-plays", JSON.stringify(next)); } catch (e) {}
      return next;
    });
    // Tell the server so admin dashboard can show real stats.
    fetch("/api/play", {
      method: "POST",
      credentials: "include",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ trackId }),
    }).catch(() => {});
  }
  function playsFor(trackId) { return playCounts[trackId] || 0; }

  useEffectA(() => {
    if (!playing || !current) return;
    const threshold = Math.min(30, current.d * 0.5);
    if (position >= threshold) maybeCountPlay(current.id);
  }, [playing, position, current?.id]);

  function openAlbum(album) { nav({ kind: "album", id: album.id }); }
  function openPlaylist(p) { nav({ kind: "playlist", id: p.id }); }
  function toggleFullscreen() { setFullscreen(f => !f); }

  useEffectA(() => {
    function onKey(e) {
      if (e.target.tagName === "INPUT") return;
      if (e.code === "Space") { e.preventDefault(); togglePlay(); }
      else if (e.code === "ArrowRight" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); next(); }
      else if (e.code === "ArrowLeft" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); prev(); }
      else if (e.code === "Escape" && fullscreen) { setFullscreen(false); }
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  });

  function onSearchChange(v) {
    setSearch(v);
    if (v.trim() && view.kind !== "search") nav({ kind: "search" });
  }

  async function logout() {
    try { await fetch("/api/logout", { method: "POST", credentials: "include" }); } catch (e) {}
    // Stop playback + clear player state
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.removeAttribute("src");
      audioRef.current.load();
    }
    lastUrlRef.current = null;
    setPlaying(false);
    setQueue([]);
    setQueueIdx(0);
    setPosition(0);
    setFullscreen(false);
    setQueueOpen(false);
    setQualityMenu(false);
    setView({ kind: "home" });
    setHistory([]);
    setForward([]);
    setMe(null);
    setLibraryLoaded(false);
  }

  const ctx = {
    me,
    queue, queueIdx, playing, position, volume, shuffle, repeat, quality,
    fullscreen, queueOpen, qualityMenu, showLyrics, likes, recentTracks,
    current, extractedMeta,
    setVolume, setShuffle, setRepeat, setQuality, setFullscreen, setQueueOpen,
    setQualityMenu, setShowLyrics, setPlaying, setView: nav, setSearch: onSearchChange,
    togglePlay, seek, next, prev, cycleRepeat, jumpQueue,
    playTrack, playAlbum, playList, playPlaylist,
    toggleLike, isLiked, addRecent,
    playsFor,
    openAlbum, openPlaylist, toggleFullscreen,
    trackById, albumOf,
    trackMeta: (t) => {
      if (!t) return {};
      const m = extractedMeta[t.id] || {};
      return {
        title: m.title || t.title,
        artist: m.artist || t.artist,
        coverUrl: m.coverBlobUrl || null,
        lyrics: m.lyrics || null,
      };
    },
    albumCoverUrl: (album) => {
      if (!album) return null;
      // server-provided cover
      if (album.cover && album.cover.hasImage) return `/api/cover/${encodeURIComponent(album.id)}`;
      return null;
    },
    share,
    openShare: (target) => setShare(target),
    closeShare: () => setShare(null),
  };

  if (!authChecked) {
    return null; // loading splash handled by boot loader
  }
  if (!me) {
    return <Login onSuccess={(m) => setMe(m)} />;
  }

  return (
    <>
      <audio ref={audioRef} preload="metadata" />
      <div className={"app " + (queueOpen ? "queue-open" : "")}>
        {isMobile && drawerOpen && (
          <div className="sb-drawer-backdrop" onClick={() => setDrawerOpen(false)} />
        )}
        <Sidebar
          view={view}
          ctx={ctx}
          onLogout={logout}
          drawerOpen={isMobile && drawerOpen}
          onCloseDrawer={() => setDrawerOpen(false)}
        />
        <main className="main">
          <TopBar ctx={ctx} view={view} search={search}
                  onBack={navBack} onFwd={navFwd}
                  canBack={history.length > 0} canFwd={forward.length > 0}
                  isMobile={isMobile}
                  onMenuOpen={() => setDrawerOpen(true)}
                  onLogout={logout} />
          <div className="main-inner">
            <ViewRouter view={view} ctx={ctx} search={search} libraryLoaded={libraryLoaded} />
          </div>
        </main>
        {queueOpen && !isMobile && !fullscreen && <QueuePanel ctx={ctx} />}
        <PlayerBar ctx={ctx} />
      </div>
      {isMobile && (
        <>
          <MobileMiniPlayer ctx={ctx} />
          <MobileBottomNav view={view} ctx={ctx} />
        </>
      )}
      <FullPlayer ctx={ctx} isMobile={isMobile} />
      <QualityMenu ctx={ctx} />
      <ShareModal ctx={ctx} />
    </>
  );
}

function MobileMiniPlayer({ ctx }) {
  const Ic = window.Icons;
  const t = ctx.current;
  const album = ctx.albumOf(t);
  if (!t) return null;
  return (
    <div className="mobile-mini" onClick={ctx.toggleFullscreen}>
      <div className="mm-cover"><Cover album={album} text={false} /></div>
      <div className="mm-info">
        <div className="mm-t">{t.title}</div>
        <div className="mm-a">{t.artist}</div>
      </div>
      <div className="mm-controls">
        <button
          className={"mm-btn heart " + (ctx.isLiked(t.id) ? "on" : "")}
          onClick={(e) => { e.stopPropagation(); ctx.toggleLike(t.id); }}
        >
          <Ic.Heart size={18} fill={ctx.isLiked(t.id) ? "currentColor" : "none"} />
        </button>
        <button
          className="mm-play"
          onClick={(e) => { e.stopPropagation(); ctx.togglePlay(); }}
        >
          {ctx.playing ? <Ic.Pause size={16} /> : <Ic.Play size={16} />}
        </button>
      </div>
      <div className="mm-progress">
        <div className="mm-progress-fill" style={{ width: `${(ctx.position / t.d) * 100}%` }} />
      </div>
    </div>
  );
}

function MobileBottomNav({ view, ctx }) {
  const Ic = window.Icons;
  const items = [
    { kind: "home",    icon: Ic.Home,    label: "Home" },
    { kind: "search",  icon: Ic.Search,  label: "Search" },
    { kind: "library", icon: Ic.Library, label: "Library" },
    { kind: "liked",   icon: Ic.Heart,   label: "Liked" },
  ];
  return (
    <nav className="mobile-nav">
      {items.map(it => {
        const Icn = it.icon;
        const active = view.kind === it.kind
          || (it.kind === "library" && (view.kind === "album" || view.kind === "playlist"));
        return (
          <button
            key={it.kind}
            className={"mobile-nav-item " + (active ? "active" : "")}
            onClick={() => ctx.setView({ kind: it.kind })}
          >
            <Icn size={20} />
            <span>{it.label}</span>
          </button>
        );
      })}
    </nav>
  );
}

function Sidebar({ view, ctx, onLogout, drawerOpen, onCloseDrawer }) {
  const Ic = window.Icons;
  const playlists = window.MUSIC_PLAYLISTS || [];
  return (
    <aside className={"sb " + (drawerOpen ? "drawer-open" : "")} data-screen-label="Sidebar">
      <div className="sb-logo">
        <div className="sb-logo-mark">
          <div className="mirrorball spinning" style={{ width: "100%", height: "100%" }} />
        </div>
        <div className="sb-logo-text">DISCO<em>·</em>THÈQUE</div>
      </div>

      <div className="sb-section">
        <div
          className={"sb-item " + (view.kind === "home" ? "active" : "")}
          onClick={() => ctx.setView({ kind: "home" })}
        ><Ic.Home size={18} />Home</div>
        <div
          className={"sb-item " + (view.kind === "search" ? "active" : "")}
          onClick={() => ctx.setView({ kind: "search" })}
        ><Ic.Search size={18} />Search</div>
        <div
          className={"sb-item " + (view.kind === "library" ? "active" : "")}
          onClick={() => ctx.setView({ kind: "library" })}
        ><Ic.Library size={18} />Your Library</div>
        <div
          className={"sb-item " + (view.kind === "recent" ? "active" : "")}
          onClick={() => ctx.setView({ kind: "recent" })}
        ><Ic.Clock size={18} />Recently Played</div>
        <div
          className={"sb-item " + (view.kind === "liked" ? "active" : "")}
          onClick={() => ctx.setView({ kind: "liked" })}
        ><Ic.Heart size={18} />Liked Songs</div>
        {ctx.me?.isAdmin && (
          <div
            className={"sb-item " + (view.kind === "admin" ? "active" : "")}
            onClick={() => ctx.setView({ kind: "admin", section: "overview" })}
            style={{ color: "var(--gold)" }}
          ><Ic.Upload size={18} />Admin</div>
        )}
      </div>

      {playlists.length > 0 && (
        <div className="sb-section" style={{ flex: 1, minHeight: 0, display: "flex", flexDirection: "column" }}>
          <div className="sb-section-label">Playlists</div>
          <div className="sb-playlists">
            {playlists.map(p => (
              <div
                key={p.id}
                className={"sb-playlist " + (view.kind === "playlist" && view.id === p.id ? "active" : "")}
                onClick={() => ctx.openPlaylist(p)}
                style={view.kind === "playlist" && view.id === p.id ? { background: "var(--bg-3)", color: "var(--ink)" } : undefined}
              >
                <div className="swatch" style={{ background: `linear-gradient(135deg, ${p.color1}, ${p.color2})` }} />
                <div style={{ minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{p.name}</div>
              </div>
            ))}
          </div>
        </div>
      )}

      <div className="sb-section" style={playlists.length === 0 ? { marginTop: "auto" } : undefined}>
        <div className="sb-item" onClick={onLogout} style={{ fontSize: 12, color: "var(--ink-4)" }}>
          <Ic.Logout size={14} />Sign Out · {ctx.me?.username}
        </div>
      </div>
    </aside>
  );
}

function TopBar({ ctx, view, search, onBack, onFwd, canBack, canFwd, isMobile, onMenuOpen, onLogout }) {
  const Ic = window.Icons;
  const initial = (ctx.me?.username || "?")[0].toUpperCase();

  if (isMobile) {
    return (
      <div className="topbar">
        {view.kind === "search" ? (
          <>
            <div className="search-input" style={{ flex: 1 }}>
              <Ic.Search size={16} />
              <input
                type="text"
                placeholder="Search…"
                value={search}
                onChange={(e) => ctx.setSearch(e.target.value)}
                autoFocus
              />
            </div>
            <button className="btn-icon" onClick={onLogout} title="Sign out" style={{ width: 36, height: 36 }}>
              <Ic.Logout size={18} />
            </button>
          </>
        ) : (
          <div className="mobile-header" style={{ flex: 1, display: "flex", alignItems: "center", gap: 10 }}>
            <div className="logo" onClick={() => ctx.setView({ kind: "home" })}>
              <div className="mark mirrorball spinning" />
              <div className="text">DISCO<em>·</em>THÈQUE</div>
            </div>
            <button className="btn-icon" onClick={() => ctx.setView({ kind: "search" })} title="Search" style={{ width: 36, height: 36 }}>
              <Ic.Search size={18} />
            </button>
            <button className="btn-icon" onClick={onLogout} title="Sign out" style={{ width: 36, height: 36 }}>
              <Ic.Logout size={18} />
            </button>
          </div>
        )}
      </div>
    );
  }

  return (
    <div className="topbar">
      <div className="topbar-nav">
        <button disabled={!canBack} onClick={onBack} style={{ opacity: canBack ? 1 : 0.4 }}>
          <Ic.ArrowLeft size={16} />
        </button>
        <button disabled={!canFwd} onClick={onFwd} style={{ opacity: canFwd ? 1 : 0.4 }}>
          <Ic.Arrow size={16} />
        </button>
      </div>
      {view.kind === "search" ? (
        <div className="search-input" style={{ flex: 1, maxWidth: 540 }}>
          <Ic.Search size={16} />
          <input
            type="text"
            placeholder="Search albums, singles, tracks…"
            value={search}
            onChange={(e) => ctx.setSearch(e.target.value)}
            autoFocus
          />
        </div>
      ) : (
        <div style={{ flex: 1 }} />
      )}
      <div className="topbar-right">
        {ctx.me?.isAdmin && (
          <button className="btn-ghost" title="Admin panel" onClick={() => ctx.setView({ kind: "admin", section: "overview" })}>
            <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
              <Ic.Upload size={12} /> Admin
            </span>
          </button>
        )}
        <div className="user-chip" onClick={onLogout} title="Sign out">
          <div className="avatar">{initial}</div>
          <span style={{ fontSize: 12 }}>{ctx.me?.username || "Member"}</span>
        </div>
      </div>
    </div>
  );
}

function ViewRouter({ view, ctx, search, libraryLoaded }) {
  if (!libraryLoaded) {
    return (
      <div style={{ padding: "80px 0", display: "grid", placeItems: "center", color: "var(--ink-3)" }}>
        <div className="mirrorball spinning" style={{ width: 40, height: 40 }} />
        <div style={{ marginTop: 14, fontFamily: "var(--f-display)", letterSpacing: "0.18em", fontSize: 12, textTransform: "uppercase", color: "var(--gold)" }}>
          Loading the floor…
        </div>
      </div>
    );
  }
  switch (view.kind) {
    case "home":     return <HomeView ctx={ctx} />;
    case "search":   return <SearchView ctx={ctx} query={search} />;
    case "library":  return <LibraryView ctx={ctx} filter={view.filter} />;
    case "album":    return <AlbumView ctx={ctx} albumId={view.id} />;
    case "playlist": return <PlaylistView ctx={ctx} playlistId={view.id} />;
    case "recent":   return <RecentView ctx={ctx} />;
    case "liked":    return <LikedView ctx={ctx} />;
    case "admin":    return ctx.me?.isAdmin
      ? <AdminView ctx={ctx} section={view.section || "overview"} />
      : <HomeView ctx={ctx} />;
    default:         return <HomeView ctx={ctx} />;
  }
}

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