function LibraryDrawer() {
  const [tab,            setTab]            = React.useState(() => {
    const caps = window.TaktLibrary ? TaktLibrary.capabilities() : {};
    return caps.canSearch === false && caps.canBrowseLists ? 'playlists' : 'search';
  });
  const [query,          setQuery]          = React.useState('');
  const [results,        setResults]        = React.useState([]);
  const [playlists,      setPlaylists]      = React.useState([]);
  const [openPlaylist,   setOpenPlaylist]   = React.useState(null); // { id, name } or null
  const [playlistTracks, setPlaylistTracks] = React.useState([]);
  const [previewTrack,   setPreviewTrack]   = React.useState(null);
  const [previewPlaying, setPreviewPlaying] = React.useState(false);
  const [searchError,    setSearchError]    = React.useState('');
  const [searching,      setSearching]      = React.useState(false);
  const [nextOffset,     setNextOffset]     = React.useState(null);
  const [loadingMore,    setLoadingMore]    = React.useState(false);
  const [loading,        setLoading]        = React.useState(false);
  const [playlistError,  setPlaylistError]  = React.useState('');
  const [isLoggedIn,     setIsLoggedIn]     = React.useState(
    () => !!(window.TaktLibrary && TaktLibrary.active().isLoggedIn())
  );
  const [previewMsg,     setPreviewMsg]     = React.useState('');
  const [computingBpm,   setComputingBpm]   = React.useState(() => new Set());
  const searchTimeout = React.useRef(null);

  // Oppdaterer en rad i begge lister den kan finnes i, etter en beregnet BPM
  // (etappe 2) — sourceId er stabil og unik, i motsetning til array-indeks.
  const patchTrackBpm = (sourceId, bpm, bpmSource) => {
    const patch = list => list.map(t => t.sourceId === sourceId ? { ...t, bpm, bpmSource } : t);
    setResults(patch);
    setPlaylistTracks(patch);
  };

  const caps = window.TaktLibrary ? TaktLibrary.capabilities() : {};
  const needsDevice = !!(window.TaktPlayer && TaktPlayer.active().capabilities.needsDevice);

  const previewSource = previewTrack && window.TaktSources ? window.TaktSources.get(previewTrack.source) : null;
  const canPreview = !!(previewSource && previewSource.player);

  // Keep login state in sync with localStorage
  React.useEffect(() => {
    const check = () => setIsLoggedIn(!!(window.TaktLibrary && TaktLibrary.active().isLoggedIn()));
    const id = setInterval(check, 2000);
    window.addEventListener('focus',   check);
    window.addEventListener('storage', check);
    return () => {
      clearInterval(id);
      window.removeEventListener('focus',   check);
      window.removeEventListener('storage', check);
    };
  }, []);

  const applySearchError = (error, detail) => {
    if (error === 'no-token') {
      setIsLoggedIn(false);
      setSearchError('Ikke innlogget — logg inn med Spotify');
    } else if (error === 401) {
      setIsLoggedIn(false);
      setSearchError('Innlogging utløpt — logg inn på nytt');
    } else if (error === 'network') {
      setSearchError('Nettverksfeil');
    } else {
      setSearchError(`Søk feilet (${error})${detail ? ': ' + detail : ''}`);
    }
  };

  // Debounced library search — owns resetting results/nextOffset on query change
  React.useEffect(() => {
    clearTimeout(searchTimeout.current);
    setSearchError('');
    setNextOffset(null);
    if (!query || tab !== 'search') { setResults([]); return; }

    setSearching(true);
    searchTimeout.current = setTimeout(async () => {
      if (!window.TaktLibrary) { setSearching(false); return; }
      const { ok, error, detail, items, nextOffset: no } = await TaktLibrary.search(query);
      setSearching(false);
      if (ok) {
        setResults(items);
        setNextOffset(no);
      } else {
        setResults([]);
        applySearchError(error, detail);
      }
    }, 400);
    return () => clearTimeout(searchTimeout.current);
  }, [query, isLoggedIn, tab]);

  const handleLoadMore = async () => {
    if (!window.TaktLibrary || nextOffset == null || loadingMore) return;
    setLoadingMore(true);
    const { ok, error, detail, items, nextOffset: no } = await TaktLibrary.search(query, { offset: nextOffset });
    setLoadingMore(false);
    if (ok) {
      setResults(prev => [...prev, ...items]);
      setNextOffset(no);
    } else {
      applySearchError(error, detail);
    }
  };

  // Load lists when tab opens
  React.useEffect(() => {
    if (tab !== 'playlists' || !isLoggedIn || !window.TaktLibrary) return;
    if (openPlaylist) return; // viewing a specific list
    setLoading(true);
    TaktLibrary.lists().then(({ ok, items, error }) => {
      setLoading(false);
      if (ok) setPlaylists(items);
      else if (error === 401 || error === 'no-token') setIsLoggedIn(false);
    });
  }, [tab, isLoggedIn, openPlaylist]);

  // Load tracks when a list (or the liked-songs pseudo-list) is opened
  React.useEffect(() => {
    if (!openPlaylist || !window.TaktLibrary) return;
    setLoading(true);
    setPlaylistError('');
    TaktLibrary.listTracks(openPlaylist.id).then(({ ok, items, error }) => {
      setLoading(false);
      if (ok) {
        setPlaylistTracks(items);
      } else {
        setPlaylistTracks([]);
        if (error === 401 || error === 'no-token') setIsLoggedIn(false);
        else setPlaylistError(error);
      }
    });
  }, [openPlaylist]);

  // Forhåndsvisning spiller via avspilleren for SPORETS EGEN kilde, ikke den
  // aktive avspilleren — å forhåndsvise en Spotify-låt skal virke selv om
  // Manuell (eller en annen kilde) er valgt som avspiller for selve timen.
  const handlePreviewToggle = async () => {
    if (!previewTrack || !canPreview) return;
    const st = previewSource.getStatus();
    if (st !== 'ready') {
      setPreviewMsg(
        st === 'needs-device' ? 'Velg en Spotify-enhet over' :
        st === 'unavailable'  ? 'Spotify Premium kreves' :
                                 'Logg inn med Spotify'
      );
      return;
    }
    if (previewPlaying) {
      previewSource.player.pause();
      setPreviewPlaying(false);
    } else {
      const r = await previewSource.player.playTrack(previewTrack, 0);
      if (r && r.ok) { setPreviewPlaying(true); setPreviewMsg(''); }
      else setPreviewMsg('Fikk ikke kontakt med enheten');
    }
  };

  const renderTrack = (r, i) => (
    <div key={i} draggable="true"
      onClick={() => setPreviewTrack(r)}
      onDragStart={e => e.dataTransfer.setData('spotify', JSON.stringify({
        title: r.title, artist: r.artist, bpm: r.bpm, bpmSource: r.bpmSource,
        durationSec: r.durationSec, fullDurationSec: r.fullDurationSec || 0,
        notes: '', source: r.source || 'none', sourceId: r.sourceId || '',
      }))}
      style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', borderBottom:`1px solid ${PC.line}`, cursor:'grab', background:previewTrack===r?`${PC.accent}0a`:'transparent' }}>
      {r.albumArt ? (
        <img src={r.albumArt} style={{ width:38, height:38, borderRadius:3, objectFit:'cover', flexShrink:0 }} alt="" />
      ) : (
        <div style={{ width:38, height:38, borderRadius:3, background:PC.bg, border:`1px solid ${PC.line}`, display:'flex', alignItems:'center', justifyContent:'center', fontSize:16, flexShrink:0, color:PC.dim }}>♪</div>
      )}
      <div style={{ minWidth:0, flex:1 }}>
        <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:13, fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', color:PC.ink }}>{r.title}</div>
        <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.muted, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{r.artist}</div>
      </div>
      <div style={{ textAlign:'right', flexShrink:0, display:'flex', alignItems:'center', gap:6 }}>
        {r.source === 'file' && !r.bpm && caps.canComputeBpm && (
          <button
            disabled={computingBpm.has(r.sourceId)}
            onClick={async e => {
              e.stopPropagation();
              if (computingBpm.has(r.sourceId)) return;
              setComputingBpm(prev => new Set(prev).add(r.sourceId));
              const res = await window.TaktSources.get(r.source)?.library?.computeBpm?.(r.sourceId);
              setComputingBpm(prev => { const n = new Set(prev); n.delete(r.sourceId); return n; });
              if (res?.ok) patchTrackBpm(r.sourceId, res.bpm, 'computed');
            }}
            style={{ fontSize:9, padding:'3px 6px', border:`1px solid ${PC.line2}`, borderRadius:3, background:'transparent', color:PC.ink2, cursor: computingBpm.has(r.sourceId) ? 'default' : 'pointer', flexShrink:0, whiteSpace:'nowrap' }}>
            {computingBpm.has(r.sourceId) ? 'Beregner…' : 'Beregn BPM'}
          </button>
        )}
        <div style={{ fontFamily:"'Space Mono',monospace", fontSize:10, color:PC.dim }}>{fmt(r.durationSec)}</div>
      </div>
    </div>
  );

  const tabBtn = (key, label) => (
    <button key={key} onClick={() => { setTab(key); setOpenPlaylist(null); }}
      style={{
        flex:1, padding:'7px 10px', borderRadius:4, fontSize:11, fontFamily:"'Archivo',sans-serif", fontWeight:700,
        textTransform:'uppercase', letterSpacing:'0.08em', cursor:'pointer',
        border:`1px solid ${tab===key?PC.accent:PC.line2}`,
        background:tab===key?`${PC.accent}10`:'transparent',
        color:tab===key?PC.accent:PC.ink2,
      }}>{label}</button>
  );

  return (
    <div style={{ width:256, minWidth:256, background:PC.paper, borderRight:`1px solid ${PC.line}`, display:'flex', flexDirection:'column', overflow:'hidden' }}>

      {/* Header */}
      <div style={{ padding:'12px 14px 10px', borderBottom:`1px solid ${PC.line}`, flexShrink:0 }}>
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:isLoggedIn ? 9 : 0 }}>
          <LibraryPicker style={{ padding:0, fontWeight:700, fontSize:10, letterSpacing:'0.16em', textTransform:'uppercase', color:PC.muted }} />
          {isLoggedIn && (
            <span style={{ fontFamily:"'Archivo',sans-serif", fontSize:10, color:PC.dim, letterSpacing:'0.1em', textTransform:'uppercase' }}>Dra til tidslinje →</span>
          )}
        </div>

        {isLoggedIn ? (
          <>
            {needsDevice && <SpotifyDevicePicker style={{ marginBottom:9 }} />}
            {(caps.canSearch || caps.canBrowseLists) && (
              <div style={{ display:'flex', gap:5, marginBottom:8 }}>
                {caps.canSearch && tabBtn('search', 'Søk')}
                {caps.canBrowseLists && tabBtn('playlists', 'Mine lister')}
              </div>
            )}
            {tab === 'search' && caps.canSearch && (
              <input value={query} onChange={e => setQuery(e.target.value)}
                placeholder="Søk etter sanger og artister…"
                style={{ width:'100%', padding:'9px 11px', background:PC.bg, border:`1px solid ${PC.line}`, borderRadius:4, fontFamily:"'Archivo',sans-serif", fontSize:13, color:PC.ink, outline:'none', boxSizing:'border-box' }} />
            )}
            {tab === 'playlists' && caps.canBrowseLists && openPlaylist && (
              <button onClick={() => { setOpenPlaylist(null); setPlaylistTracks([]); }}
                style={{ display:'flex', alignItems:'center', gap:5, padding:'6px 8px', borderRadius:4, border:'none', background:'transparent', color:PC.ink2, fontFamily:"'Archivo',sans-serif", fontWeight:700, fontSize:11, cursor:'pointer' }}>
                ← Tilbake til lister
              </button>
            )}
          </>
        ) : (
          <div style={{ marginTop:12, marginBottom:2 }}>
            <button onClick={() => window.TaktLibrary?.login()}
              style={{ width:'100%', padding:'10px', borderRadius:6, border:'none', background:PC.accent, color:'#fff', fontFamily:"'Archivo',sans-serif", fontWeight:700, fontSize:13, letterSpacing:'0.06em', cursor:'pointer' }}>
              {window.TaktLibrary ? TaktLibrary.statusMessage() : 'Logg inn'}
            </button>
            <div style={{ marginTop:8, fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.muted, lineHeight:1.5 }}>
              Krever Spotify Premium
            </div>
          </div>
        )}
      </div>

      {/* Content */}
      <div style={{ overflowY:'auto', flex:1 }}>
        {loading && (
          <div style={{ padding:'20px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted }}>Laster…</div>
        )}

        {searching && (
          <div style={{ padding:'20px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted }}>Søker…</div>
        )}

        {!searching && searchError && (
          <div style={{ margin:'12px 10px', padding:'10px 12px', borderRadius:4, background:`${PC.z5}12`, border:`1px solid ${PC.z5}40` }}>
            <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.z5, fontWeight:600 }}>{searchError}</div>
            {(searchError.includes('innlogget') || searchError.includes('utløpt')) && (
              <button onClick={() => window.TaktLibrary?.login()}
                style={{ marginTop:8, padding:'6px 12px', borderRadius:4, border:`1px solid ${PC.accent}`, background:'transparent', color:PC.accent, fontFamily:"'Archivo',sans-serif", fontWeight:700, fontSize:11, cursor:'pointer' }}>
                Logg inn på nytt
              </button>
            )}
          </div>
        )}

        {/* SEARCH TAB */}
        {tab === 'search' && caps.canSearch && !searching && !searchError && isLoggedIn && !query && (
          <div style={{ padding:'32px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted, lineHeight:1.7 }}>
            Søk etter sanger eller artister
          </div>
        )}
        {tab === 'search' && caps.canSearch && !searching && results.map(renderTrack)}
        {tab === 'search' && caps.canSearch && !searching && !searchError && results.length === 0 && query && (
          <div style={{ padding:'24px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted }}>Ingen treff</div>
        )}
        {tab === 'search' && caps.canSearch && !searching && nextOffset !== null && results.length > 0 && (
          <button onClick={handleLoadMore} disabled={loadingMore}
            style={{
              display:'block', width:'calc(100% - 20px)', margin:'10px', padding:'9px 10px', borderRadius:4,
              fontSize:11, fontFamily:"'Archivo',sans-serif", fontWeight:700, letterSpacing:'0.08em',
              cursor: loadingMore ? 'default' : 'pointer',
              border:`1px solid ${PC.line2}`, background:'transparent', color:PC.ink2,
              opacity: loadingMore ? 0.6 : 1,
            }}>
            {loadingMore ? 'Laster…' : 'Vis flere'}
          </button>
        )}

        {/* LISTS TAB — list of playlists */}
        {tab === 'playlists' && caps.canBrowseLists && !openPlaylist && !loading && (
          <>
            {/* Liked songs entry — always first, only when the library has one */}
            {caps.hasLikedList && (
              <div onClick={() => setOpenPlaylist({ id: '__liked__', name: 'Likte sanger' })}
                style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', borderBottom:`1px solid ${PC.line}`, cursor:'pointer', background:`${PC.accent}06` }}>
                <div style={{ width:38, height:38, borderRadius:3, background:`linear-gradient(135deg, ${PC.accent}, #e63900)`, display:'flex', alignItems:'center', justifyContent:'center', fontSize:18, flexShrink:0, color:'#fff' }}>♥</div>
                <div style={{ minWidth:0, flex:1 }}>
                  <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:13, fontWeight:700, color:PC.ink }}>Likte sanger</div>
                  <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.muted }}>Dine ❤-merkede sanger</div>
                </div>
                <span style={{ color:PC.dim, fontSize:14, flexShrink:0 }}>›</span>
              </div>
            )}

            {playlists.map(p => (
              <div key={p.id} onClick={() => setOpenPlaylist({ id: p.id, name: p.name })}
                style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', borderBottom:`1px solid ${PC.line}`, cursor:'pointer' }}>
                {p.image ? (
                  <img src={p.image} style={{ width:38, height:38, borderRadius:3, objectFit:'cover', flexShrink:0 }} alt="" />
                ) : (
                  <div style={{ width:38, height:38, borderRadius:3, background:PC.bg, border:`1px solid ${PC.line}`, display:'flex', alignItems:'center', justifyContent:'center', fontSize:16, flexShrink:0, color:PC.dim }}>♫</div>
                )}
                <div style={{ minWidth:0, flex:1 }}>
                  <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:13, fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', color:PC.ink }}>{p.name}</div>
                  <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.muted, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{p.owner || '—'}</div>
                </div>
                <span style={{ color:PC.dim, fontSize:14, flexShrink:0 }}>›</span>
              </div>
            ))}

            <div style={{ padding:'14px 14px 10px', fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.dim, lineHeight:1.6 }}>
              Spotify har låst spilleliste-innhold for utvikler-apper. Bruk «Likte sanger» eller søk for å finne musikk.
            </div>
          </>
        )}
        {tab === 'playlists' && caps.canBrowseLists && !openPlaylist && !loading && playlists.length === 0 && isLoggedIn && (
          <div style={{ padding:'32px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted }}>Ingen spillelister funnet</div>
        )}

        {/* LISTS TAB — tracks of opened list */}
        {tab === 'playlists' && caps.canBrowseLists && openPlaylist && !loading && (
          <>
            <div style={{ padding:'10px 14px', fontFamily:"'Archivo',sans-serif", fontWeight:800, fontSize:13, color:PC.ink, borderBottom:`1px solid ${PC.line}` }}>{openPlaylist.name}</div>
            {playlistError && (
              <div style={{ margin:'12px 10px', padding:'10px 12px', borderRadius:4, background:`${PC.z5}12`, border:`1px solid ${PC.z5}40` }}>
                <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.z5, fontWeight:600, lineHeight:1.5 }}>{playlistError}</div>
              </div>
            )}
            {!playlistError && playlistTracks.map(renderTrack)}
            {!playlistError && playlistTracks.length === 0 && (
              <div style={{ padding:'24px 14px', textAlign:'center', fontFamily:"'Archivo',sans-serif", fontSize:12, color:PC.muted }}>Tom spilleliste</div>
            )}
          </>
        )}
      </div>

      {/* Preview bar */}
      <div style={{ padding:'10px 14px', borderTop:`1px solid ${PC.line}`, background:PC.bg, flexShrink:0 }}>
        <div style={{ fontFamily:"'Archivo',sans-serif", fontWeight:700, fontSize:9, letterSpacing:'0.14em', textTransform:'uppercase', color:PC.muted, marginBottom:6 }}>Forhåndsvisning</div>
        {previewTrack && canPreview ? (
          <>
            <div style={{ fontFamily:"'Archivo',sans-serif", fontSize:12, fontWeight:600, color:PC.ink, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', marginBottom:5 }}>{previewTrack.title}</div>
            <div style={{ display:'flex', alignItems:'center', gap:10 }}>
              <button onClick={handlePreviewToggle}
                style={{ width:28, height:28, borderRadius:'50%', background:PC.accent, color:'#fff', border:'none', fontSize:11, cursor:'pointer', flexShrink:0 }}>
                {previewPlaying ? '❚❚' : '▶'}
              </button>
              <div style={{ flex:1, height:3, background:PC.line, borderRadius:2 }} />
              <span style={{ fontFamily:"'Space Mono',monospace", fontSize:10, color:PC.muted, flexShrink:0 }}>{fmt(previewTrack.durationSec)}</span>
            </div>
            {previewMsg && (
              <div style={{ marginTop:6, fontFamily:"'Archivo',sans-serif", fontSize:11, color:PC.z3, fontWeight:600 }}>{previewMsg}</div>
            )}
          </>
        ) : (
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <div style={{ width:28, height:28, borderRadius:'50%', background:'rgba(22,20,15,0.06)', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, color:PC.dim, flexShrink:0 }}>▶</div>
            <div style={{ flex:1, height:3, background:PC.line, borderRadius:2 }} />
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { LibraryDrawer });
