// Persistent bottom-of-viewport playback bar.
//
// Mounted once in the app shell (app.jsx) and rendered on top of every
// route. Pulls all of its state from `useAudio()` so it stays in sync
// with whatever played the current track — could be the home discover
// list, a profile track row, a review-queue mini-player.
//
// Hides itself entirely when nothing is loaded so a fresh session
// doesn't have a dead bar floating at the bottom of an empty screen.
//
// Layout (top to bottom):
//   1. Full-width sunset scrub bar — clickable to seek
//   2. mm:ss / mm:ss timestamps (mono, tabular)
//   3. Cover + title/artist  •  transport (shuffle/prev/play/next/repeat)
//      •  like + volume slider
//
// The transport / shuffle / repeat buttons are present visually but
// not yet wired (muted styling). The play button works; prev/next
// would need a queue concept we don't have yet — left as TODO.

function NowPlayingBar() {
  const audio = useAudio();
  const likes = useLikes();
  const track = audio.currentTrack;
  // Expanded "big player" mode — when toggled, the panel grows
  // upward from the bottom edge and shows the waveform + scrolling
  // lyrics on top of the standard transport row. Collapsing returns
  // to the compact strip without restarting playback.
  const [expanded, setExpanded] = React.useState(false);
  // Auto-collapse when the player closes entirely (track cleared),
  // so re-opening a different track later doesn't reuse stale state.
  React.useEffect(() => { if (!track) setExpanded(false); }, [track]);

  // Expose a global setter so the share-link boot path in app.jsx
  // can pop the big player open after fetching + playing the shared
  // track. Cleared on unmount in case the bar ever stops mounting.
  React.useEffect(() => {
    window.expandBigPlayer = () => setExpanded(true);
    return () => { delete window.expandBigPlayer; };
  }, []);

  if (!track) return null;

  const duration = audio.duration || track.duration || 0;
  const pct = duration ? Math.max(0, Math.min(1, audio.pos / duration)) * 100 : 0;

  return (
    // Container is centred + capped so the player doesn't stretch
    // edge-to-edge on big monitors. `max-width: 80vw` keeps the bar
    // hugging the centre of attention while still scaling on narrow
    // screens via the 1200px cap; the larger of the two caps wins on
    // ultrawide displays so we never exceed 80% of the viewport.
    <div style={{
      position: 'fixed', left: '50%', bottom: 18, zIndex: 30,
      transform: 'translateX(-50%)',
      width: 'min(80vw, 1200px)',
      pointerEvents: 'none',
    }}>
      <div style={{ pointerEvents: 'auto' }}>
      {/* Big-player panel — only mounted when expanded. Sits visually
          above the compact bar via a -8px negative margin on the
          Glass below so the two cards read as one connected surface
          when expanded. */}
      {expanded && <BigPlayerPanel track={track} audio={audio} duration={duration}/>}
      <Glass radius={20} padding={0} style={{
        background: 'rgba(28,14,55,0.92)',
        boxShadow: 'var(--shadow-pop)',
        border: '1px solid var(--border-medium)',
        // ALWAYS set the top corners + top border explicitly so React
        // doesn't have to undo previous-render overrides. Earlier we
        // used `...(expanded ? {…} : {})` which omitted the keys when
        // collapsed — that resets the corresponding longhand props to
        // empty, but the `border-radius` shorthand doesn't re-cascade
        // back into the longhand, so the corners stayed sharp after a
        // collapse. Explicit values both ways = no surprise.
        borderTopLeftRadius:  expanded ? 0 : 20,
        borderTopRightRadius: expanded ? 0 : 20,
        borderTop: expanded ? 'none' : '1px solid var(--border-medium)',
      }}>

        {/* Progress row — mm:ss · [================O============] · mm:ss
            Timestamps live INSIDE the row now (one on each side) so
            the player only takes two visual rows instead of three.
            Slider expands to fill the remaining space. */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 12,
          padding: '12px 20px 4px',
        }}>
          <span style={{
            fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)',
            fontVariantNumeric: 'tabular-nums', minWidth: 40, textAlign: 'right',
          }}>{fmt(audio.pos)}</span>

          <div
            onClick={(e) => {
              const rect = e.currentTarget.getBoundingClientRect();
              const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
              audio.seek(ratio * duration);
            }}
            style={{ flex: 1, position: 'relative', cursor: 'pointer', padding: '6px 0' }}
          >
            <div style={{
              position: 'relative', height: 8, borderRadius: 4,
              background: 'rgba(196,154,255,0.16)',
            }}>
              <div style={{
                position: 'absolute', left: 0, top: 0, bottom: 0,
                width: `${pct}%`, background: 'var(--grad-sunset)', borderRadius: 4,
                transition: 'width 200ms linear',
              }}/>
              <div style={{
                position: 'absolute', left: `${pct}%`, top: '50%',
                transform: 'translate(-50%, -50%)',
                width: 16, height: 16, borderRadius: '50%', background: 'var(--foam)',
                boxShadow: '0 0 0 4px rgba(255,247,236,0.15), var(--glow-coral)',
              }}/>
            </div>
          </div>

          <span style={{
            fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--fg-3)',
            fontVariantNumeric: 'tabular-nums', minWidth: 40, textAlign: 'left',
          }}>{fmt(duration)}</span>
        </div>

        {/* Controls row */}
        <div style={{
          display: 'flex', alignItems: 'center', gap: 18, padding: '10px 20px 14px',
        }}>

          {/* Cover + meta — clickable as a no-op for now; could route
              to a "full screen now playing" view in the future. */}
          <div style={{
            display: 'flex', alignItems: 'center', gap: 14,
            width: 240, minWidth: 0,
          }}>
            <Cover colors={track.cover} src={track.coverUrl} size={54} radius={12}/>
            <div style={{ minWidth: 0, flex: 1 }}>
              <div style={{
                fontSize: 13.5, fontWeight: 700, color: 'var(--foam)',
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
              }}>{track.title}</div>
              <div style={{
                fontSize: 11.5, color: 'var(--fg-3)',
                whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
              }}>{track.artist}{duration ? ` · ${fmt(duration)}` : ''}</div>
            </div>
          </div>

          {/* Transport (centre) */}
          <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10 }}>
            <TransportBtn icon="shuffle" muted title="Shuffle (kommt bald)"/>
            <TransportBtn icon="backward-step" muted title="Voriger (kommt bald)"/>
            <button
              onClick={() => audio.play(track)}
              aria-label={audio.isPlaying ? 'Pause' : 'Abspielen'}
              style={{
                width: 46, height: 46, borderRadius: '50%',
                background: 'var(--grad-lila)', border: 'none', cursor: 'pointer',
                color: 'var(--foam)', display: 'flex', alignItems: 'center', justifyContent: 'center',
                boxShadow: 'var(--glow-lila)',
              }}
            >
              <ApIcon name={audio.isPlaying ? 'pause' : 'play'} size={15}
                style={{ marginLeft: audio.isPlaying ? 0 : 2 }}/>
            </button>
            <TransportBtn icon="forward-step" muted title="Nächster (kommt bald)"/>
            <TransportBtn icon="repeat" muted title="Wiederholen (kommt bald)"/>
          </div>

          {/* Like + add-to-playlist + volume + expand toggle (right).
              Heart + plus both write to ingame-synced tables — a
              heart here shows up in the phone next time the player
              reconnects, same for playlist adds. */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, width: 320, justifyContent: 'flex-end' }}>
            <button
              onClick={() => likes.toggle(track.id)}
              title={likes.isLiked(track.id) ? 'Like entfernen' : 'Liken'}
              aria-label={likes.isLiked(track.id) ? 'Like entfernen' : 'Liken'}
              style={{
                background: 'transparent', border: 'none', cursor: 'pointer',
                color: likes.isLiked(track.id) ? 'var(--coral)' : 'var(--fg-3)',
                padding: 6, display: 'flex',
                transition: 'color 200ms var(--ease-out), transform 200ms var(--ease-spring)',
              }}
            >
              <ApIcon name="heart" size={16}/>
            </button>
            <AddToPlaylistButton trackId={track.id}/>
            {/* Share — copies a /#/track/<id> deep-link to clipboard.
                Whoever opens the link gets the track auto-played and
                the big player popped open via the boot handler in
                app.jsx. */}
            <ShareButton track={track} size={28}/>
            <button
              onClick={() => setExpanded(v => !v)}
              title={expanded ? 'Player einklappen' : 'Großer Player'}
              aria-label={expanded ? 'Player einklappen' : 'Großer Player'}
              style={{
                background: expanded ? 'rgba(169,114,244,0.18)' : 'transparent',
                border: expanded ? '1px solid rgba(169,114,244,0.4)' : '1px solid var(--border-soft)',
                cursor: 'pointer', color: expanded ? 'var(--lila-200)' : 'var(--fg-2)',
                padding: '7px 10px', borderRadius: 10, display: 'flex',
              }}
            >
              <ApIcon name={expanded ? 'chevron-down' : 'chevron-up'} size={13}/>
            </button>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, width: 180 }}>
              <button
                onClick={() => audio.setVolume(audio.volume > 0 ? 0 : 0.6)}
                aria-label="Stumm schalten"
                style={{
                  background: 'transparent', border: 'none', cursor: 'pointer',
                  color: 'var(--fg-2)', padding: 4, display: 'flex',
                }}
              >
                <ApIcon name={audio.volume === 0 ? 'volume-mute' : audio.volume < 0.5 ? 'volume-low' : 'volume-high'} size={14}/>
              </button>
              <Slider value={audio.volume} max={1} onChange={audio.setVolume}
                style={{ flex: 1, height: 4 }} accent="var(--lila-400)"/>
            </div>
            <button
              onClick={() => audio.stop()}
              title="Schließen"
              aria-label="Player schließen"
              style={{
                background: 'transparent', border: 'none', cursor: 'pointer',
                color: 'var(--fg-3)', padding: 6, display: 'flex',
              }}
            >
              <ApIcon name="xmark" size={14}/>
            </button>
          </div>

        </div>
      </Glass>
      </div>
    </div>
  );
}

// Process-lifetime cache of lyrics responses, keyed by trackId.
// Re-opening the big player on the same track is then instant — no
// network round-trip. Cleared from the AdminMenu regenerate-lyrics
// path (`window.invalidateLyrics(id)`) so admins forcing a fresh
// transcription don't see stale results.
const lyricsCache = new Map();
window.invalidateLyrics = (id) => { lyricsCache.delete(id); };

// Expanded "big player" panel that pops up above the compact strip.
// Three-column layout: cover + meta on the left, lyrics in the
// middle (scrollable, auto-syncing to the current playback time),
// waveform on the right. ~360px tall, the compact strip below
// brings it to ~440px total which fits comfortably without
// dominating the page.
function BigPlayerPanel({ track, audio, duration }) {
  // Lyrics fetch — uses the per-trackId cache to avoid re-fetching
  // when the user collapses + re-opens the panel on the same track.
  // `lyricsCache.get()` returns undefined for an unfetched track,
  // null for "fetched and got nothing" (still in transcription
  // queue), or the row object for a real result — both undefined
  // and a missing-from-cache check below trigger a fresh fetch.
  const [lyrics, setLyrics] = React.useState(() =>
    track?.id && lyricsCache.has(track.id) ? lyricsCache.get(track.id) : null
  );
  React.useEffect(() => {
    if (!track?.id) return;
    // Cache hit: render straight from the Map, skip the network.
    if (lyricsCache.has(track.id)) {
      setLyrics(lyricsCache.get(track.id));
      return;
    }
    let cancelled = false;
    (async () => {
      const out = await Api.lyrics(track.id);
      if (cancelled) return;
      lyricsCache.set(track.id, out);
      setLyrics(out);
    })();
    return () => { cancelled = true; };
  }, [track?.id]);

  return (
    <Glass radius={20} padding={0} style={{
      background: 'rgba(28,14,55,0.92)',
      boxShadow: 'var(--shadow-pop)',
      border: '1px solid var(--border-medium)',
      // Square off the bottom so the compact strip can dock.
      borderBottomLeftRadius: 0,
      borderBottomRightRadius: 0,
      borderBottom: '1px solid var(--border-hairline)',
      overflow: 'hidden',
    }}>
      <div style={{
        display: 'grid',
        gridTemplateColumns: '220px 1fr 280px',
        gap: 20, padding: 20, height: 380,
      }}>

        {/* Cover + meta column */}
        <div style={{
          display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 16,
        }}>
          <Cover colors={track.cover} src={track.coverUrl}
            size={200} radius={20}
            style={{ boxShadow: '0 18px 48px rgba(20,8,40,0.55)' }}/>
          <div style={{ minWidth: 0, width: '100%' }}>
            <Eyebrow style={{ marginBottom: 6 }}>Jetzt läuft</Eyebrow>
            <div style={{
              fontSize: 18, fontWeight: 800, color: 'var(--foam)',
              letterSpacing: '-0.01em', lineHeight: 1.15,
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
            }}>{track.title}</div>
            <div style={{
              fontSize: 13.5, color: 'var(--fg-2)', marginTop: 4,
              whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
            }}>{track.artist}</div>

            {/* Tag chips — clickable, jumps to the home library
                pre-filtered by that tag. Useful for "find more like
                this" discovery without leaving playback. Only top 4
                tags render to keep the column tidy. */}
            {(track.tags || []).length > 0 && (
              <div style={{
                display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 10,
              }}>
                {track.tags.slice(0, 4).map(t => (
                  <button key={t}
                    onClick={() => window.openHomeWithTag?.(t)}
                    title={`Mehr Tracks mit „${t}"`}
                    style={{
                      display: 'inline-flex', alignItems: 'center', padding: '3px 9px',
                      borderRadius: 999, background: 'rgba(196,154,255,0.10)',
                      color: 'var(--lila-200)', border: '1px solid var(--border-soft)',
                      fontSize: 10.5, fontWeight: 600, letterSpacing: 0.2,
                      whiteSpace: 'nowrap', cursor: 'pointer', fontFamily: 'inherit',
                    }}>{t}</button>
                ))}
              </div>
            )}
          </div>
        </div>

        {/* Lyrics — scrolling, current-line highlighted */}
        <LyricsView lyrics={lyrics} pos={audio.pos} onSeek={audio.seek}/>

        {/* Waveform — interactive, click to seek */}
        <WaveformView peaks={track.peaks || []} pos={audio.pos} duration={duration}
          onSeek={audio.seek}/>
      </div>
    </Glass>
  );
}

// Lyrics list. Each line has `t` (timestamp seconds) and `line`
// (text). Auto-scrolls the active line into view; clicking a line
// seeks playback there. Falls back to a placeholder when lyrics
// are missing or still being transcribed.
function LyricsView({ lyrics, pos, onSeek }) {
  const containerRef = React.useRef(null);
  const lines = lyrics?.lines || [];

  // Find the currently-active line — the one with the largest `t`
  // that's still ≤ current position. Binary search would be tidier
  // for very long songs but linear over <500 lines runs in
  // microseconds.
  const activeIdx = React.useMemo(() => {
    let idx = -1;
    for (let i = 0; i < lines.length; i++) {
      if (lines[i].t <= pos) idx = i;
      else break;
    }
    return idx;
  }, [lines, pos]);

  // Auto-scroll the active line into view. We use scrollTop math
  // instead of scrollIntoView so the container itself scrolls (not
  // the page) and we get smooth behaviour via CSS scroll-behavior.
  React.useEffect(() => {
    if (activeIdx < 0 || !containerRef.current) return;
    const el = containerRef.current.querySelector(`[data-line="${activeIdx}"]`);
    if (!el) return;
    const c = containerRef.current;
    // Target: keep the active line ~40% from the top so the user
    // sees what's coming next as well as what just played.
    const target = el.offsetTop - c.clientHeight * 0.4;
    c.scrollTo({ top: target, behavior: 'smooth' });
  }, [activeIdx]);

  if (!lyrics) {
    return (
      <PlaceholderLyrics text="Lyrics werden geladen…"/>
    );
  }
  if (lyrics.status === 'pending' || lyrics.status === 'in_progress') {
    return (
      <PlaceholderLyrics text="Lyrics werden gerade transkribiert. Versuch's gleich nochmal."/>
    );
  }
  if (!lines.length) {
    return (
      <PlaceholderLyrics text={lyrics.error
        ? 'Transkription fehlgeschlagen.'
        : 'Keine Lyrics für diesen Track.'}/>
    );
  }

  return (
    <div ref={containerRef} style={{
      position: 'relative', overflowY: 'auto',
      // Fade the top + bottom edges so cut-off lines feel natural
      // instead of abruptly clipping. Two stacked mask gradients —
      // one for the fade-out at the top, one at the bottom.
      maskImage: 'linear-gradient(180deg, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%)',
      WebkitMaskImage: 'linear-gradient(180deg, transparent 0, #000 28px, #000 calc(100% - 28px), transparent 100%)',
    }}>
      <div style={{ padding: '12px 0' }}>
        {lines.map((l, i) => {
          const isActive = i === activeIdx;
          const isPast   = i < activeIdx;
          return (
            <button key={i} data-line={i}
              onClick={() => onSeek?.(l.t)}
              style={{
                display: 'block', width: '100%', textAlign: 'left',
                background: 'transparent', border: 'none', padding: '6px 10px',
                fontFamily: 'inherit', cursor: 'pointer',
                fontSize: isActive ? 18 : 14.5,
                fontWeight: isActive ? 800 : 500,
                lineHeight: 1.35, letterSpacing: '-0.005em',
                color: isActive ? 'var(--foam)' : (isPast ? 'var(--fg-4)' : 'var(--fg-2)'),
                opacity: isActive ? 1 : (isPast ? 0.45 : 0.85),
                transition: 'all 240ms var(--ease-out)',
              }}
            >
              {l.line}
            </button>
          );
        })}
      </div>
    </div>
  );
}

function PlaceholderLyrics({ text }) {
  return (
    <div style={{
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      flexDirection: 'column', gap: 10,
      color: 'var(--fg-3)', fontSize: 13.5, lineHeight: 1.55, textAlign: 'center',
      padding: 24,
    }}>
      <ApIcon name="music" size={22} color="var(--fg-4)"/>
      {text}
    </div>
  );
}

// Waveform — vertical bars rendered from the `peaks` array (120
// ints 0-100 extracted by ffmpeg at upload time). Bars left of the
// current play position are coloured by the sunset gradient (via
// per-bar background); bars right of it sit muted. Click anywhere
// on the waveform to seek to that position.
function WaveformView({ peaks, pos, duration, onSeek }) {
  if (!peaks || peaks.length === 0) {
    // No waveform data — happens for tracks uploaded before the
    // peak-extraction pipeline existed, or if ffmpeg failed silently
    // at submit time. Show a calm idle pattern so the column doesn't
    // look broken.
    return (
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        color: 'var(--fg-4)', fontSize: 12, textAlign: 'center', padding: 12,
      }}>
        Keine Waveform verfügbar.
      </div>
    );
  }

  // Position-as-fraction → how many peaks should render in the
  // "played" colour. Round down so a peak only flips colour once
  // the playhead has truly passed it.
  const playedCount = duration > 0
    ? Math.floor((pos / duration) * peaks.length)
    : 0;

  return (
    <div
      onClick={(e) => {
        if (!duration) return;
        const rect = e.currentTarget.getBoundingClientRect();
        const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
        onSeek?.(ratio * duration);
      }}
      style={{
        display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 2,
        cursor: 'pointer', height: '100%',
      }}
    >
      {peaks.map((p, i) => {
        const isPlayed = i < playedCount;
        // Bars are at least 4 % tall so silent / very-quiet samples
        // still show as a hairline. The 0.65 scale factor caps a
        // peak=100 bar at 65 % of the column so even the loudest
        // moment doesn't span the entire height — looks more like a
        // waveform readout, less like a barcode.
        const h = Math.max(4, p * 0.65);
        return (
          <div key={i} style={{
            width: 3,
            height: `${h}%`,
            borderRadius: 2,
            background: isPlayed ? 'var(--grad-sunset)' : 'rgba(196,154,255,0.20)',
            transition: 'background 150ms var(--ease-out)',
          }}/>
        );
      })}
    </div>
  );
}

// Add-to-playlist button. Click → popover with the user's playlists
// + "Neue Playlist" option at the bottom. Pick one → track gets
// appended via the existing API. Portalled to body so it doesn't
// clip against the player's overflow:hidden Glass wrapper.
function AddToPlaylistButton({ trackId }) {
  const playlists = useMyPlaylists();
  const [open, setOpen] = React.useState(false);
  const [rect, setRect] = React.useState(null);
  const btnRef = React.useRef(null);
  const popRef = React.useRef(null);

  React.useEffect(() => {
    if (!open) return;
    if (btnRef.current) setRect(btnRef.current.getBoundingClientRect());
    const onClick = (e) => {
      if (btnRef.current?.contains(e.target)) return;
      if (popRef.current?.contains(e.target)) return;
      setOpen(false);
    };
    setTimeout(() => document.addEventListener('click', onClick), 0);
    return () => document.removeEventListener('click', onClick);
  }, [open]);

  const onPick = async (id) => {
    setOpen(false);
    try {
      await MyPlaylists.addTrack(id, trackId);
      toast.success('Zur Playlist hinzugefügt.');
    } catch (e) {
      toast.error('Hinzufügen fehlgeschlagen: ' + e.message);
    }
  };

  const onCreate = async () => {
    setOpen(false);
    const name = prompt('Neue Playlist · wie soll sie heißen?');
    if (!name || !name.trim()) return;
    try {
      const fresh = await MyPlaylists.create(name.trim());
      const created = (fresh || []).find(p => p.name === name.trim());
      if (created) {
        await MyPlaylists.addTrack(created.id, trackId);
        toast.success(`Playlist „${name.trim()}" angelegt mit Track.`);
      }
    } catch (e) {
      toast.error('Anlegen fehlgeschlagen: ' + e.message);
    }
  };

  const POP_WIDTH = 240;
  const popover = open && rect ? (
    <div ref={popRef} style={{
      position: 'fixed',
      // The compact player lives at the bottom of the viewport so
      // the popover always opens upward — anchor to bottom-of-button.
      bottom: window.innerHeight - rect.top + 6,
      left:   Math.max(8, rect.right - POP_WIDTH),
      width:  POP_WIDTH, maxHeight: 320,
      zIndex: 1000,
      background: 'rgba(28,14,55,0.95)',
      backdropFilter: 'blur(20px) saturate(140%)',
      WebkitBackdropFilter: 'blur(20px) saturate(140%)',
      border: '1px solid var(--border-medium)',
      borderRadius: 14, padding: 6,
      boxShadow: 'var(--shadow-pop)',
      display: 'flex', flexDirection: 'column',
      overflow: 'hidden',
    }}>
      <div style={{
        fontSize: 10, fontWeight: 700, letterSpacing: '0.18em',
        textTransform: 'uppercase', color: 'var(--fg-4)',
        padding: '8px 10px 4px',
      }}>Zur Playlist hinzufügen</div>

      <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
        {playlists.length === 0 ? (
          <div style={{ fontSize: 12, color: 'var(--fg-3)', padding: '10px', textAlign: 'center' }}>
            Noch keine Playlists.
          </div>
        ) : playlists.map(p => (
          <PopoverItem key={p.id} onClick={() => onPick(p.id)}
            icon="list" color="var(--lila-300)">
            <span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
              {p.name}
            </span>
            <span style={{ fontSize: 10.5, color: 'var(--fg-4)', fontVariantNumeric: 'tabular-nums' }}>
              {p.trackCount}
            </span>
          </PopoverItem>
        ))}
      </div>

      <div style={{ height: 1, background: 'var(--border-hairline)', margin: '4px 0' }}/>
      <PopoverItem onClick={onCreate} icon="plus" color="var(--success)">
        Neue Playlist…
      </PopoverItem>
    </div>
  ) : null;

  return (
    <>
      <button
        ref={btnRef}
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
        title="Zur Playlist hinzufügen"
        aria-label="Zur Playlist hinzufügen"
        style={{
          background: open ? 'rgba(169,114,244,0.18)' : 'transparent',
          border: open ? '1px solid rgba(169,114,244,0.4)' : 'none',
          cursor: 'pointer', color: open ? 'var(--lila-200)' : 'var(--fg-3)',
          padding: 6, borderRadius: 8, display: 'flex',
        }}
      >
        <ApIcon name="plus" size={16}/>
      </button>
      {popover && ReactDOM.createPortal(popover, document.body)}
    </>
  );
}

// Helper for items inside the AddToPlaylist popover. Same shape as
// AdminMenu items but a notch more compact — lives in the player
// chrome so it needs to fit a smaller surface.
function PopoverItem({ icon, color, onClick, children }) {
  const [hover, setHover] = React.useState(false);
  return (
    <button onClick={onClick}
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
      style={{
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '7px 10px', borderRadius: 8,
        background: hover ? 'rgba(196,154,255,0.10)' : 'transparent',
        border: 'none', cursor: 'pointer',
        color: 'inherit', fontSize: 13, fontWeight: 500,
        textAlign: 'left', fontFamily: 'inherit', width: '100%',
      }}
    >
      <ApIcon name={icon} size={12} color={color}/>
      {children}
    </button>
  );
}

// Small transport button used for shuffle/repeat/prev/next. `muted`
// dims the colour so the disabled ones don't compete with the active
// play/pause button in the middle.
function TransportBtn({ icon, onClick, muted, title }) {
  return (
    <button
      onClick={onClick}
      title={title}
      style={{
        background: 'transparent', border: 'none', cursor: onClick ? 'pointer' : 'default',
        width: 36, height: 36, borderRadius: '50%',
        color: muted ? 'var(--fg-3)' : 'var(--foam)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        opacity: muted ? 0.5 : 1,
      }}
    >
      <ApIcon name={icon} size={muted ? 13 : 15}/>
    </button>
  );
}

window.NowPlayingBar = NowPlayingBar;
