bostrom.network/src/components/MusicPlayer.tsx

import { useEffect, useRef, useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Volume2, VolumeX, Repeat, Download, Music, ChevronUp, ChevronDown } from 'lucide-react';
import { Slider } from '@/components/ui/slider';

declare global {
  interface Window {
    __bostromBgAudio?: HTMLAudioElement;
  }
}

const AUDIO_SRC = '/audio/background-music.mp3';

const getGlobalAudio = (): HTMLAudioElement | null => {
  if (typeof window === 'undefined') return null;
  if (!window.__bostromBgAudio) {
    const audio = new Audio(AUDIO_SRC);
    audio.preload = 'auto';
    window.__bostromBgAudio = audio;
  }
  return window.__bostromBgAudio;
};

const formatTime = (seconds: number): string => {
  if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${secs.toString().padStart(2, '0')}`;
};

export const MusicPlayer = () => {
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const [isPlaying, setIsPlaying] = useState(true);
  const [isMuted, setIsMuted] = useState(false);
  const [volume, setVolume] = useState(30);
  const [isLooping, setIsLooping] = useState(true);
  const [isExpanded, setIsExpanded] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);

  // Ensure there's only ONE audio instance (prevents overlap on hot reload / remount)
  useEffect(() => {
    const audio = getGlobalAudio();
    if (!audio) return;
    audioRef.current = audio;

    // Apply initial settings
    audio.loop = isLooping;
    audio.volume = volume / 100;
    audio.muted = isMuted;

    // Keep UI in sync with the audio element
    const onTimeUpdate = () => setCurrentTime(audio.currentTime);
    const onLoadedMetadata = () => setDuration(audio.duration || 0);
    const onDurationChange = () => setDuration(audio.duration || 0);
    const onPlay = () => setIsPlaying(true);
    const onPause = () => setIsPlaying(false);

    audio.addEventListener('timeupdate', onTimeUpdate);
    audio.addEventListener('loadedmetadata', onLoadedMetadata);
    audio.addEventListener('durationchange', onDurationChange);
    audio.addEventListener('play', onPlay);
    audio.addEventListener('pause', onPause);

    setCurrentTime(audio.currentTime || 0);
    setDuration(audio.duration || 0);

    let interactionHandler: (() => void) | null = null;

    const addInteractionListeners = (handler: () => void) => {
      document.addEventListener('click', handler);
      document.addEventListener('keydown', handler);
      document.addEventListener('touchstart', handler);
    };

    const removeInteractionListeners = (handler: () => void) => {
      document.removeEventListener('click', handler);
      document.removeEventListener('keydown', handler);
      document.removeEventListener('touchstart', handler);
    };

    const tryPlay = async () => {
      // Don't restart if already playing
      if (!audio.paused) {
        setIsPlaying(true);
        return;
      }

      // Some browsers block autoplay with sound. Best effort:
      // 1) try normal play
      // 2) if blocked -> start muted immediately, then enable sound on first user interaction
      let startedMuted = false;

      try {
        await audio.play();
        setIsPlaying(true);
        return;
      } catch {
        // Autoplay with sound was prevented
      }

      try {
        startedMuted = true;
        audio.muted = true;
        setIsMuted(true);
        await audio.play();
        setIsPlaying(true);
      } catch {
        setIsPlaying(false);
      }

      // Always install an interaction handler to either (a) start playback or (b) enable sound
      // after browsers' autoplay restrictions.
      interactionHandler = () => {
        if (audio.paused) {
          audio.play().then(() => setIsPlaying(true)).catch(() => {});
        }
        if (startedMuted) {
          audio.muted = false;
          setIsMuted(false);
        }
        removeInteractionListeners(interactionHandler!);
        interactionHandler = null;
      };
      addInteractionListeners(interactionHandler);
    };

    // Wait for audio to be ready before playing
    if (audio.readyState >= 2) {
      void tryPlay();
    } else {
      const onCanPlay = () => {
        void tryPlay();
        audio.removeEventListener('canplay', onCanPlay);
      };
      audio.addEventListener('canplay', onCanPlay);
    }

    return () => {
      // Don't pause audio on cleanup - keep it playing across re-renders
      audio.removeEventListener('timeupdate', onTimeUpdate);
      audio.removeEventListener('loadedmetadata', onLoadedMetadata);
      audio.removeEventListener('durationchange', onDurationChange);
      audio.removeEventListener('play', onPlay);
      audio.removeEventListener('pause', onPause);
      if (interactionHandler) {
        removeInteractionListeners(interactionHandler);
      }
    };
  }, []);

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

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    audio.loop = isLooping;
  }, [isLooping]);

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    audio.muted = isMuted;
  }, [isMuted]);

  const togglePlay = () => {
    const audio = audioRef.current;
    if (!audio) return;
    if (audio.paused) {
      audio.play().then(() => setIsPlaying(true)).catch(() => {});
    } else {
      audio.pause();
      setIsPlaying(false);
    }
  };

  const toggleMute = () => {
    setIsMuted((prev) => {
      const next = !prev;
      const audio = audioRef.current;
      if (audio) audio.muted = next;
      return next;
    });
  };

  const handleVolumeChange = (value: number[]) => {
    const newVolume = value[0];
    setVolume(newVolume);
    const audio = audioRef.current;
    if (audio) {
      audio.volume = newVolume / 100;
      if (newVolume === 0) audio.muted = true;
      if (newVolume > 0 && isMuted) audio.muted = false;
    }
    if (newVolume === 0) setIsMuted(true);
    else if (isMuted) setIsMuted(false);
  };

  const handleSeek = (value: number[]) => {
    const newTime = value[0];
    const audio = audioRef.current;
    if (!audio || !duration) return;
    audio.currentTime = newTime;
    setCurrentTime(newTime);
  };

  const toggleLoop = () => {
    setIsLooping((v) => !v);
  };

  const handleDownload = () => {
    const link = document.createElement('a');
    link.href = '/audio/background-music.mp3';
    link.download = 'bostrom-soundtrack.mp3';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  };

  return (
    <>
      <motion.div
        initial={{ opacity: 0, x: 100 }}
        animate={{ opacity: 1, x: 0 }}
        transition={{ duration: 0.5, delay: 1.5 }}
        className="fixed bottom-6 right-6 z-50"
      >
        <div className="bg-background/90 backdrop-blur-md border border-border rounded-xl overflow-hidden box-glow-primary">
          {/* Collapsed state - just play button */}
          <div className="flex items-center gap-3 p-3">
            <button
              onClick={togglePlay}
              className={`w-10 h-10 rounded-full flex items-center justify-center transition-all ${
                isPlaying 
                  ? 'bg-primary text-primary-foreground' 
                  : 'bg-muted text-muted-foreground hover:bg-primary/20 hover:text-primary'
              }`}
            >
              <Music className="w-5 h-5" />
            </button>
            
            <span className="font-play text-sm text-muted-foreground hidden sm:block">
              {isPlaying ? 'Now Playing' : 'Play Music'}
            </span>

            <button
              onClick={() => setIsExpanded(!isExpanded)}
              className="p-2 text-muted-foreground hover:text-primary transition-colors"
            >
              {isExpanded ? <ChevronDown className="w-4 h-4" /> : <ChevronUp className="w-4 h-4" />}
            </button>
          </div>

          {/* Expanded controls */}
          <AnimatePresence>
            {isExpanded && (
              <motion.div
                initial={{ height: 0, opacity: 0 }}
                animate={{ height: 'auto', opacity: 1 }}
                exit={{ height: 0, opacity: 0 }}
                transition={{ duration: 0.2 }}
                className="border-t border-border overflow-hidden"
              >
                <div className="p-4 space-y-4">
                  {/* Timeline / Seek control */}
                  <div className="space-y-2">
                    <Slider
                      value={[currentTime]}
                      onValueChange={handleSeek}
                      max={duration || 100}
                      step={0.1}
                      disabled={!duration}
                      className="w-full"
                    />
                    <div className="flex justify-between text-xs font-mono text-muted-foreground">
                      <span>{formatTime(currentTime)}</span>
                      <span>{formatTime(duration)}</span>
                    </div>
                  </div>

                  {/* Volume control */}
                  <div className="flex items-center gap-3">
                    <button
                      onClick={toggleMute}
                      className="p-2 text-muted-foreground hover:text-primary transition-colors"
                    >
                      {isMuted || volume === 0 ? (
                        <VolumeX className="w-5 h-5" />
                      ) : (
                        <Volume2 className="w-5 h-5" />
                      )}
                    </button>
                    <Slider
                      value={[isMuted ? 0 : volume]}
                      onValueChange={handleVolumeChange}
                      max={100}
                      step={1}
                      className="flex-1"
                    />
                    <span className="font-mono text-xs text-muted-foreground w-8 text-right">
                      {isMuted ? 0 : volume}%
                    </span>
                  </div>

                  {/* Action buttons */}
                  <div className="flex items-center justify-between">
                    <button
                      onClick={toggleLoop}
                      className={`flex items-center gap-2 px-3 py-2 rounded-lg transition-all ${
                        isLooping 
                          ? 'bg-primary/20 text-primary' 
                          : 'text-muted-foreground hover:text-primary hover:bg-primary/10'
                      }`}
                    >
                      <Repeat className="w-4 h-4" />
                      <span className="font-play text-xs">Repeat</span>
                    </button>

                    <button
                      onClick={handleDownload}
                      className="flex items-center gap-2 px-3 py-2 rounded-lg text-muted-foreground hover:text-primary hover:bg-primary/10 transition-all"
                    >
                      <Download className="w-4 h-4" />
                      <span className="font-play text-xs">Download</span>
                    </button>
                  </div>
                </div>
              </motion.div>
            )}
          </AnimatePresence>
        </div>
      </motion.div>
    </>
  );
};

Neighbours