import SpotifyWebApi from 'spotify-web-api-js'; import { SpotifyTrack, SpotifyUser, RecentlyPlayedItem, SpotifyPlaylist } from '../types'; const SPOTIFY_CLIENT_ID = (import.meta as any).env.VITE_SPOTIFY_CLIENT_ID; // Use same-origin callback for prod proxy server; env can override const REDIRECT_URI = (import.meta as any).env.VITE_REDIRECT_URI || (typeof window !== 'undefined' ? `${window.location.origin}/callback.html` : 'http://localhost:3000/callback.html'); // Debug: Check what redirect URI is being used console.log('🔍 Debug - Current redirect URI:', REDIRECT_URI); console.log('🔍 Debug - Environment VITE_REDIRECT_URI:', (import.meta as any).env.VITE_REDIRECT_URI); export const spotifyApi = new SpotifyWebApi(); export const getSpotifyAuthUrl = (): string => { const scopes = [ 'user-read-private', 'user-read-email', 'user-read-recently-played', 'user-top-read', 'playlist-read-private', 'playlist-read-collaborative', 'playlist-modify-public', 'playlist-modify-private', 'user-read-playback-state', 'user-modify-playback-state', 'user-read-currently-playing', 'ugc-image-upload', // Required for uploading playlist cover images ].join(' '); const params = new URLSearchParams({ client_id: SPOTIFY_CLIENT_ID, response_type: 'code', redirect_uri: REDIRECT_URI, scope: scopes, show_dialog: 'true', }); const authUrl = `https://accounts.spotify.com/authorize?${params.toString()}`; console.log('🔍 Debug - Generated Spotify Auth URL:', authUrl); return authUrl; }; export const exchangeCodeForToken = async (code: string): Promise<{ access_token: string; refresh_token: string; expires_in: number; }> => { const body = new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: REDIRECT_URI, }); console.log('🔍 Spotify - Token exchange request:', { code: code.substring(0, 20) + '...', redirect_uri: REDIRECT_URI, client_id: SPOTIFY_CLIENT_ID }); const response = await fetch('https://accounts.spotify.com/api/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${btoa(`${SPOTIFY_CLIENT_ID}:${(import.meta as any).env.VITE_SPOTIFY_CLIENT_SECRET}`)}`, }, body: body, }); console.log('🔍 Spotify - Token exchange response:', { status: response.status, statusText: response.statusText, ok: response.ok }); if (!response.ok) { const errorText = await response.text(); console.error('🔍 Spotify - Token exchange error:', errorText); throw new Error(`Failed to exchange code for token: ${response.status} ${response.statusText}`); } const tokenData = await response.json(); console.log('🔍 Spotify - Token exchange success:', !!tokenData.access_token); return tokenData; }; export const refreshAccessToken = async (refreshToken: string): Promise<{ access_token: string; expires_in: number; }> => { const response = await fetch('https://accounts.spotify.com/api/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${btoa(`${SPOTIFY_CLIENT_ID}:${(import.meta as any).env.VITE_SPOTIFY_CLIENT_SECRET}`)}`, }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, }), }); if (!response.ok) { throw new Error('Failed to refresh access token'); } return response.json(); }; export const initializeSpotifyApi = (accessToken: string): void => { spotifyApi.setAccessToken(accessToken); }; export const fetchUserProfile = async (accessToken: string): Promise => { spotifyApi.setAccessToken(accessToken); return spotifyApi.getMe(); }; export const fetchRecentlyPlayed = async (accessToken: string, limit = 20): Promise => { spotifyApi.setAccessToken(accessToken); const response = await spotifyApi.getMyRecentlyPlayedTracks({ limit }); return response.items; }; export const fetchTopTracks = async (accessToken: string, timeRange = 'short_term', limit = 20): Promise => { spotifyApi.setAccessToken(accessToken); const response = await spotifyApi.getMyTopTracks({ time_range: timeRange, limit }); return response.items; }; export const fetchTopArtists = async (accessToken: string, timeRange = 'short_term', limit = 20) => { spotifyApi.setAccessToken(accessToken); return spotifyApi.getMyTopArtists({ time_range: timeRange, limit }); }; export const createPlaylist = async ( accessToken: string, userId: string, name: string, description: string ): Promise => { spotifyApi.setAccessToken(accessToken); const playlist = await spotifyApi.createPlaylist(userId, { name, description, public: false, }); return playlist; }; export const addTracksToPlaylist = async ( accessToken: string, playlistId: string, trackUris: string[] ): Promise => { spotifyApi.setAccessToken(accessToken); await spotifyApi.addTracksToPlaylist(playlistId, trackUris); }; export const playTrack = async (accessToken: string, trackUri: string): Promise => { spotifyApi.setAccessToken(accessToken); await spotifyApi.play({ uris: [trackUri], }); }; export const pausePlayback = async (accessToken: string): Promise => { spotifyApi.setAccessToken(accessToken); await spotifyApi.pause(); }; export const getCurrentPlayback = async (accessToken: string) => { spotifyApi.setAccessToken(accessToken); return spotifyApi.getMyCurrentPlaybackState(); }; // Utility function to extract track features for playlist generation export const analyzeTrackFeatures = (tracks: SpotifyTrack[]) => { const features = { genres: new Set(), artists: new Set(), avgPopularity: 0, totalDuration: 0, decades: new Set(), }; tracks.forEach(track => { // Extract genres from artists (simplified) track.artists.forEach(artist => features.artists.add(artist.name)); // Calculate average popularity features.avgPopularity += track.popularity; // Add duration features.totalDuration += track.duration_ms; // Estimate decade from album name or other clues (simplified) // This would typically use more sophisticated analysis }); features.avgPopularity /= tracks.length; return features; }; // Generate mixed playlist based on both users' music export const generateMixedPlaylist = ( user1Tracks: SpotifyTrack[], user2Tracks: SpotifyTrack[], playlistName: string = "Our Mixed Vibes" ): SpotifyTrack[] => { const allTracks = [...user1Tracks, ...user2Tracks]; // Remove duplicates based on track ID const uniqueTracks = allTracks.filter((track, index, self) => index === self.findIndex(t => t.id === track.id) ); // Sort by popularity and mix them const sortedByPopularity = uniqueTracks.sort((a, b) => b.popularity - a.popularity); // Take top tracks and shuffle them for variety const topTracks = sortedByPopularity.slice(0, 30); const shuffled = topTracks.sort(() => Math.random() - 0.5); return shuffled.slice(0, 25); // Return 25 tracks for the playlist };