import { MusicUtil } from "./util";
import { MusicController } from "./controller";
import { MusicStore } from "./store";
import { MusicClient } from "./client";
import {
    PlayerDevice,
    Track,
    PlayerType,
    PlayerContext,
    TrackStatus,
    Artist,
    PlayerName,
    CodyResponse,
} from "./models";
import { AudioStat } from "./audiostat";
import { getUnixTime } from "date-fns";

const musicStore = MusicStore.getInstance();
const musicClient = MusicClient.getInstance();
const audioStat = AudioStat.getInstance();
const musicController = MusicController.getInstance();
const musicUtil = new MusicUtil();

export const SPOTIFY_LIKED_SONGS_PLAYLIST_NAME = "Liked Songs";

export class MusicPlayerState {
    private static instance: MusicPlayerState;
    private constructor() {
        //
    }
    static getInstance() {
        if (!MusicPlayerState.instance) {
            MusicPlayerState.instance = new MusicPlayerState();
        }
        return MusicPlayerState.instance;
    }

    async isWindowsSpotifyRunning(): Promise<boolean> {
        /**
         * /tasklist /fi "imagename eq Spotify.exe" /fo list /v |find " - "
         * Window Title: Dexys Midnight Runners - Come On Eileen
         */
        let result = await musicUtil
            .execCmd(MusicController.WINDOWS_SPOTIFY_TRACK_FIND)
            .catch((e) => {
                return null;
            });
        if (result && result.toLowerCase().includes("title")) {
            return true;
        }
        return false;
    }

    async isSpotifyWebRunning(): Promise<boolean> {
        let accessToken = musicStore.spotifyAccessToken;
        if (accessToken) {
            let spotifyDevices: PlayerDevice[] = await this.getSpotifyDevices();
            if (spotifyDevices.length > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * returns...
     * {
        "devices" : [ {
            "id" : "5fbb3ba6aa454b5534c4ba43a8c7e8e45a63ad0e",
            "is_active" : false,
            "is_private_session": true,
            "is_restricted" : false,
            "name" : "My fridge",
            "type" : "Computer",
            "volume_percent" : 100
        } ]
        }
     */
    async getSpotifyDevices(): Promise<PlayerDevice[]> {
        let devices = [];
        const accessToken = musicStore.spotifyAccessToken;
        if (!accessToken) {
            return [];
        }

        const api = "/v1/me/player/devices";
        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (response.data && response.data.devices) {
            devices = response.data.devices;
        }

        return devices || [];
    }

    /**
     * returns i.e.
     * track = {
            artist: 'Bob Dylan',
            album: 'Highway 61 Revisited',
            disc_number: 1,
            duration: 370,
            played count: 0,
            track_number: 1,
            starred: false,
            popularity: 71,
            id: 'spotify:track:3AhXZa8sUQht0UEdBJgpGc',
            name: 'Like A Rolling Stone',
            album_artist: 'Bob Dylan',
            artwork_url: 'http://images.spotify.com/image/e3d720410b4a0770c1fc84bc8eb0f0b76758a358',
            spotify_url: 'spotify:track:3AhXZa8sUQht0UEdBJgpGc' }
        }
    */
    async getWindowsSpotifyTrackInfo() {
        let windowTitleStr = "Window Title:";
        // get the artist - song name from the command result, then get the rest of the info from spotify
        let songInfo = await musicUtil
            .execCmd(MusicController.WINDOWS_SPOTIFY_TRACK_FIND)
            .catch((e) => {
                return null;
            });
        if (!songInfo || !songInfo.includes(windowTitleStr)) {
            // it must have paused, or an ad, or it was closed
            return null;
        }
        // fetch it from spotify
        // result will be something like: "Window Title: Dexys Midnight Runners - Come On Eileen"
        songInfo = songInfo.substring(windowTitleStr.length);
        let artistSong = songInfo.split("-");
        let artist = artistSong[0].trim();
        let song = artistSong[1].trim();

        const qParam = encodeURIComponent(`artist:${artist} track:${song}`);
        const qryStr = `q=${qParam}&type=track&limit=2&offset=0`;
        let api = `/v1/search?${qryStr}`;
        let resp = await musicClient.spotifyApiGet(api);
        let trackInfo = null;
        if (
            musicUtil.isResponseOk(resp) &&
            resp.data &&
            resp.data.tracks &&
            resp.data.tracks.items
        ) {
            trackInfo = resp.data.tracks.items[0];
            // set the other attributes like start and type
            trackInfo["type"] = "spotify";
            trackInfo["state"] = "playing";
            trackInfo["start"] = 0;
            trackInfo["end"] = 0;
            trackInfo["genre"] = "";
        }

        return trackInfo;
    }

    async getSpotifyTracks(
        ids: string[],
        includeArtistData: boolean = false,
        includeAudioFeaturesData: boolean = false,
        includeGenre: boolean = false
    ): Promise<Track[]> {
        const finalIds: string[] = [];
        ids.forEach((id) => {
            id = musicUtil.createSpotifyIdFromUri(id);
            if (id) {
                finalIds.push(id);
            }
        });
        const tracksToReturn: Track[] = [];
        const api = `/v1/tracks`;
        const qsOptions = { ids: finalIds.join(",") };

        let response = await musicClient.spotifyApiGet(api, qsOptions);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api, qsOptions);
        }

        // get the features if the flag is set to true
        let spotifyAudioFeaturesP = null;
        if (includeAudioFeaturesData) {
            // async - call the spotify api to fetch the audio features
            spotifyAudioFeaturesP = audioStat
                .getSpotifyAudioFeatures(ids)
                .catch((e) => {
                    return null;
                });
        }

        if (response && response.status === 200 && response.data) {
            // get the tracks
            let artistIdMap: any = {};
            const tracks: any[] = response.data.tracks || [];
            tracks.forEach((trackData: Track) => {
                const track: Track = musicUtil.copySpotifyTrackToCodyTrack(
                    trackData
                );
                track.progress_ms = response.data.progress_ms
                    ? response.data.progress_ms
                    : 0;

                if (track.artists) {
                    track.artists.forEach((artist: any) => {
                        artistIdMap[artist.id] = artist.id;
                    });
                }

                tracksToReturn.push(track);
            });

            // fetch the artists from spotify if this flag is set to true
            if (includeArtistData) {
                let artistIds = Object.keys(artistIdMap).map(key => key);

                // fetch the artists all at once or in batches
                let artists: any[] = [];
                if (artistIds) {
                    // spotify's limit is 50, so batch if it's greater than 50
                    if (artistIds.length > 50) {
                        while (artistIds.length) {
                            // keep removing from the artistIds 50 at a time
                            let splicedArtistIds = artistIds.splice(0, 50);

                            const batchedArtists = await this.getSpotifyArtistsByIds(
                                splicedArtistIds
                            );
                            if (batchedArtists && batchedArtists.length) {
                                artists.push(...batchedArtists);
                            }
                        }
                    } else {
                        artists = await this.getSpotifyArtistsByIds(artistIds);
                    }
                }

                // populate the artists into the existing tracks
                if (artists && artists.length) {
                    tracksToReturn.forEach((t: Track) => {
                        // get the artist IDs from the tracks to return shallow artists array
                        const trackArtistIds: string[] = t.artists.map((artist: any) => artist.id);

                        // filter out the full artists found in the artists response
                        // based on the artist IDs that were in the tracksToReturn
                        const artistsForTrack: any[] = artists.filter(
                            (n: any) => trackArtistIds.includes(n.id)
                        );

                        if (artistsForTrack && artistsForTrack.length) {
                            // replace the shallow artists with the full artists
                            t.artists = artistsForTrack;
                        }

                        if (!t.genre && includeGenre) {
                            // first check if we have an artist in artists
                            let genre = "";
                            if (t.artists && t.artists.length) {
                                for (let artistCandidate of t.artists) {
                                    if (
                                        artistCandidate.genres &&
                                        artistCandidate.genres.length
                                    ) {
                                        try {
                                            genre = musicClient.getHighestFrequencySpotifyGenre(
                                                artistCandidate.genres
                                            );
                                        } catch (e) {
                                            //
                                        }
                                        break;
                                    }
                                }
                            }
                            if (genre) {
                                t.genre = genre;
                            }
                        }
                    });
                }
            }

            // get the features if the flag is set to true
            if (includeAudioFeaturesData) {
                // await for the call we made earlier
                const spotifyAudioFeatures = await spotifyAudioFeaturesP;
                if (spotifyAudioFeatures && spotifyAudioFeatures.length) {
                    // "id": "4JpKVNYnVcJ8tuMKjAj50A",
                    // "uri": "spotify:track:4JpKVNYnVcJ8tuMKjAj50A",
                    // track.features = spotifyAudioFeatures[0];
                    spotifyAudioFeatures.forEach((feature: any) => {
                        const uri: string = feature.uri;
                        const foundTrack = tracksToReturn.find(
                            (t: Track) => t.uri === uri
                        );
                        if (foundTrack) {
                            foundTrack.features = feature;
                        }
                    });
                }
            }
        }

        return tracksToReturn;
    }

    async getSpotifyTrackById(
        id: string,
        includeArtistData: boolean = false,
        includeAudioFeaturesData: boolean = false,
        includeGenre: boolean = false
    ): Promise<Track> {
        id = musicUtil.createSpotifyIdFromUri(id);
        let track: Track;
        let api = `/v1/tracks/${id}`;

        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (response && response.status === 200 && response.data) {
            track = musicUtil.copySpotifyTrackToCodyTrack(response.data);
            track.progress_ms = response.data.progress_ms
                ? response.data.progress_ms
                : 0;

            // get the arist data
            if (includeArtistData && track.artists) {
                let artists: Artist[] = [];

                for (let i = 0; i < track.artists.length; i++) {
                    const artist = track.artists[i];
                    const artistData: Artist = await this.getSpotifyArtistById(
                        artist.id
                    );
                    artists.push(artistData);
                }
                if (artists.length > 0) {
                    track.artists = artists;
                } else {
                    track.artists = [];
                }
            }

            if (!track.genre && includeGenre) {
                // first check if we have an artist in artists
                // artists[0].genres[0]

                let genre = "";
                if (
                    track.artists &&
                    track.artists.length > 0 &&
                    track.artists[0].genres
                ) {
                    // make sure we use the highest frequency genre
                    genre = musicClient.getHighestFrequencySpotifyGenre(
                        track.artists[0].genres
                    );
                }
                if (!genre) {
                    // get the genre
                    genre = await musicController.getGenre(
                        track.artist,
                        track.name
                    );
                }
                if (genre) {
                    track.genre = genre;
                }
            }

            // get the features
            if (includeAudioFeaturesData) {
                const spotifyAudioFeatures = await audioStat.getSpotifyAudioFeatures(
                    [id]
                );
                if (spotifyAudioFeatures && spotifyAudioFeatures.length > 0) {
                    track.features = spotifyAudioFeatures[0];
                }
            }
        } else {
            track = new Track();
        }

        return track;
    }

    async getSpotifyArtistsByIds(ids: string[]): Promise<Artist[]> {
        let artists: Artist[] = [];

        ids = musicUtil.createSpotifyIdsFromUris(ids);

        let api = `/v1/artists`;
        // const qParam = { ids };
        // just create a comma separated list of these
        if (ids && ids.length) {
            api = `${api}?ids=${ids.join(",")}`;
        }

        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (response && response.status === 200 && response.data) {
            artists = response.data.artists || [];
        }

        return artists;
    }

    async getSpotifyArtistById(id: string): Promise<Artist> {
        let artist: Artist = new Artist();

        id = musicUtil.createSpotifyIdFromUri(id);

        let api = `/v1/artists/${id}`;

        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (response && response.status === 200 && response.data) {
            const artistData = response.data;
            // delete external_urls
            delete artistData.external_urls;
            artist = artistData;
        }

        return artist;
    }

    async getSpotifyWebCurrentTrack(): Promise<Track> {
        let track: Track;

        let api = "/v1/me/player/currently-playing";
        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (
            response &&
            response.status === 200 &&
            response.data &&
            response.data.item
        ) {
            const data = response.data;
            track = musicUtil.copySpotifyTrackToCodyTrack(data.item);
            track.progress_ms = data.progress_ms ? data.progress_ms : 0;

            // set the actions ("actions": {"disallows": {"resuming": true}})
            track.actions = data.actions;

            // set whether this track is playing or not
            /**
             * data: {
                context:null
                currently_playing_type:"track"
                is_playing:true
                item:Object {album: Object, artists: Array(1), available_markets: Array(79), …}
                progress_ms:153583
                timestamp:1583797755729
            }
            */
            const isPlaying =
                data.is_playing !== undefined && data.is_playing !== null
                    ? data.is_playing
                    : false;
            if (track.uri && track.uri.includes("spotify:ad:")) {
                track.state = TrackStatus.Advertisement;
            } else {
                track.state = isPlaying
                    ? TrackStatus.Playing
                    : TrackStatus.Paused;
            }
        } else {
            track = new Track();
            track.state = TrackStatus.NotAssigned;
            track.httpStatus = response.status;
        }

        return track;
    }

    async getSpotifyRecentlyPlayedTracksBefore(
        limit: number = 50,
        before: number = 0
    ): Promise<CodyResponse> {
        return this.fetchSpotifyReentlyPlayedTracksData(limit, 0, before);
    }

    async getSpotifyRecentlyPlayedTracksAfter(
        limit: number = 50,
        after: number = 0
    ): Promise<CodyResponse> {
        return this.fetchSpotifyReentlyPlayedTracksData(limit, after, 0);
    }

    async getSpotifyRecentlyPlayedTracks(
        limit: number = 50,
        after: number = 0,
        before: number = 0
    ): Promise<Track[]> {
        const resp: CodyResponse = await this.fetchSpotifyReentlyPlayedTracksData(
            limit,
            after,
            before
        );
        if (resp && resp.data && resp.data.tracks) {
            return resp.data.tracks;
        }
        return [];
    }

    /**
     * Fetch the recently played tracks data
     * @param limit (max of 1000)
     * @param after
     * @param before
     */
    async fetchSpotifyReentlyPlayedTracksData(
        limit: number = 50,
        after: number = 0,
        before: number = 0
    ): Promise<CodyResponse> {
        let api = "/v1/me/player/recently-played";
        const qsOptions: any = {};

        // max # of tracks for pagination
        let trackLimit = limit;
        if (trackLimit <= 0 || trackLimit > 1000) {
            trackLimit = 1000;
        }

        // spotify api limit is 50
        let apiLimit = limit;
        if (apiLimit <= 0 || apiLimit > 50) {
            apiLimit = 50;
        }

        // set the spotify per api limit
        qsOptions["limit"] = apiLimit;

        if (after && after > 0) {
            qsOptions["after"] = after;
        } else if (before && before > 0) {
            qsOptions["before"] = before;
        }
        let resp: CodyResponse = await musicClient.spotifyApiGet(
            api,
            qsOptions
        );
        // check if the token needs to be refreshed
        if (resp.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            resp = await musicClient.spotifyApiGet(api, qsOptions);
        }
        let tracks: Track[] = [];
        if (musicUtil.isItemsResponseOk(resp)) {
            resp.data.items.forEach((item: any) => {
                const track: Track = musicUtil.copySpotifyTrackToCodyTrack(
                    item.track
                );
                // set the context info
                if (item.context) {
                    track.context_type = item.context.type;
                    track.context_uri = item.context.uri;
                }
                track.played_at = item.played_at;
                track.played_at_utc_seconds = getUnixTime(item.played_at);
                tracks.push(track);
            });

            let cursors = resp.data.cursors;
            if (cursors) {
                cursors.before = parseInt(cursors.before, 10);
                cursors.after = parseInt(cursors.after, 10);
            } else {
                cursors = {
                    before: 0,
                    after: 0,
                };
            }

            if (resp.data.next && tracks.length < trackLimit) {
                // continue fetching until we've reached the limit
                let reachedLimit = false;
                let nextApi = resp.data.next;
                while (!reachedLimit) {
                    let nextCursors = resp.data.cursors;
                    if (nextCursors && cursors) {
                        // update the before and after
                        const prevBefore = cursors.before;
                        const before = parseInt(nextCursors.before, 10);
                        if (before < prevBefore || prevBefore === 0) {
                            cursors.before = before;
                        }

                        const prevAfter = cursors.after;
                        const after = parseInt(nextCursors.after, 10);
                        if (after > prevAfter || prevAfter === 0) {
                            cursors.after = after;
                        }
                    }
                    if (!nextApi) {
                        reachedLimit = true;
                        break;
                    }

                    // get the next api
                    api = nextApi.substring(
                        nextApi.indexOf("/v1"),
                        nextApi.length
                    );
                    const nextResults = await musicClient.spotifyApiGet(api);
                    if (musicUtil.isItemsResponseOk(nextResults)) {
                        nextApi = nextResults.data.next;
                        if (nextResults.data.items.length > 0) {
                            nextResults.data.items.forEach((item: any) => {
                                let spotifyTrack = item.track;
                                const track: Track = musicUtil.copySpotifyTrackToCodyTrack(
                                    spotifyTrack
                                );
                                track.played_at = item.played_at;
                                track.played_at_utc_seconds = getUnixTime(item.played_at);
                                tracks.push(track);
                            });
                        } else {
                            reachedLimit = true;
                        }
                    } else {
                        reachedLimit = true;
                    }

                    if (tracks.length >= trackLimit) {
                        reachedLimit = true;
                    }
                }
            }

            // update the cursors
            resp.data.cursors = cursors;
        }

        if (resp.data) {
            delete resp.data.items;
            delete resp.data.href;
            delete resp.data.next;
            delete resp.data.limit;
            // add tracks to the response
            resp.data["tracks"] = tracks;
        }

        return resp;
    }

    async getRecommendationsForTracks(
        seed_tracks: string[] = [],
        limit: number = 40,
        market: string = "",
        min_popularity: number = 20,
        target_popularity: number = 90,
        seed_genres: string[] = [],
        seed_artists: string[] = [],
        features: any = {}
    ) {
        let tracks: Track[] = [];

        // change the trackIds to non-uri ids
        seed_tracks = musicUtil.createTrackIdsFromUris(seed_tracks);
        // the create trackIds will create normal artist ids as well
        seed_artists = musicUtil.createTrackIdsFromUris(seed_artists);
        // it can only take up to 5, remove the rest
        if (seed_tracks.length > 5) {
            seed_tracks.length = 5;
        }
        if (seed_genres.length > 5) {
            seed_genres.length = 5;
        }
        if (seed_artists.length > 5) {
            seed_artists.length = 5;
        }
        const qsOptions: any = {
            limit,
            min_popularity,
            target_popularity,
        };
        if (seed_genres.length) {
            qsOptions["seed_genres"] = seed_genres.join(",");
        }
        if (seed_tracks.length) {
            qsOptions["seed_tracks"] = seed_tracks.join(",");
        }
        if (seed_artists.length) {
            qsOptions["seed_artists"] = seed_artists.join(",");
        }
        if (market) {
            qsOptions["market"] = market;
        }
        const featureKeys = Object.keys(features);
        if (featureKeys.length) {
            featureKeys.forEach((key) => {
                qsOptions[key] = features[key];
            });
        }
        const api = `/v1/recommendations`;

        // add to the api to prevent the querystring from escaping the comma

        let response = await musicClient.spotifyApiGet(api, qsOptions);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api, qsOptions);
        }

        if (musicUtil.isResponseOk(response)) {
            tracks = response.data.tracks;
        }

        return tracks;
    }

    async setMute(mute: boolean, device_id = ""): Promise<CodyResponse> {
        const api = `/v1/me/player/volume`;

        if (musicStore.prevVolumePercent === 0) {
            // get the previous volume
            const devices: PlayerDevice[] = await this.getSpotifyDevices();

            const playerContext: PlayerContext = await this.getSpotifyPlayerContext();

            if (playerContext && playerContext.device) {
                musicStore.prevVolumePercent =
                    playerContext.device.volume_percent;
            }
            if (playerContext.device.volume_percent === 0) {
                musicStore.prevVolumePercent = 45;
            }
        }

        let qsOptions: any = {
            volume_percent: mute ? 0 : musicStore.prevVolumePercent,
        };
        if (device_id) {
            qsOptions["device_id"] = device_id;
        }
        let codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});

        // check if the token needs to be refreshed
        if (codyResp.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});
        }

        return codyResp;
    }

    async setShuffle(shuffle: boolean, device_id = ""): Promise<CodyResponse> {
        const api = `/v1/me/player/shuffle`;
        let qsOptions: any = {
            state: shuffle,
        };
        if (device_id) {
            qsOptions["device_id"] = device_id;
        }
        let codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});

        // check if the token needs to be refreshed
        if (codyResp.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});
        }

        return codyResp;
    }

    async setRepeatOff(device_id: string = ""): Promise<CodyResponse> {
        return await this.setRepeat("off", device_id);
    }

    async setTrackRepeat(device_id: string = ""): Promise<CodyResponse> {
        return await this.setRepeat("track", device_id);
    }

    async setPlaylistRepeat(device_id: string = ""): Promise<CodyResponse> {
        return await this.setRepeat("context", device_id);
    }

    async updateRepeatMode(
        setToOn: boolean,
        device_id: string = ""
    ): Promise<CodyResponse> {
        const state = setToOn ? "track" : "off";

        return await this.setRepeat(state, device_id);
    }

    async setRepeat(
        state: string,
        device_id: string = ""
    ): Promise<CodyResponse> {
        const api = `/v1/me/player/repeat`;
        let qsOptions: any = {
            state,
        };
        if (device_id) {
            qsOptions["device_id"] = device_id;
        }
        let codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});

        // check if the token needs to be refreshed
        if (codyResp.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            codyResp = await musicClient.spotifyApiPut(api, qsOptions, {});
        }

        return codyResp;
    }

    async getSpotifyPlayerContext(): Promise<PlayerContext> {

        let playerContext: PlayerContext = new PlayerContext();
        let api = "/v1/me/player";
        let response = await musicClient.spotifyApiGet(api);

        // check if the token needs to be refreshed
        if (response.status === 401) {
            // refresh the token
            await musicClient.refreshSpotifyToken();
            // try again
            response = await musicClient.spotifyApiGet(api);
        }

        if (
            response &&
            response.status === 200 &&
            response.data &&
            response.data.item
        ) {
            // override "type" with "spotify"
            response.data.item["type"] = "spotify";
            response.data.item["playerType"] = PlayerType.WebSpotify;
            musicUtil.extractAristFromSpotifyTrack(response.data.item);
            playerContext = response.data;
        }
        return playerContext;
    }

    async launchAndPlaySpotifyTrack(
        trackId: string = "",
        playlistId: string = "",
        playerName: PlayerName = PlayerName.SpotifyWeb
    ) {
        // check if there's any spotify devices
        const spotifyDevices: PlayerDevice[] = await this.getSpotifyDevices();

        if (!spotifyDevices || spotifyDevices.length === 0) {
            // no spotify devices found, lets launch the web player with the track

            // launch it
            await this.launchWebPlayer(playerName);

            // now select it from within the playlist within 2 seconds
            await setTimeout(() => {
                this.playSpotifyTrackFromPlaylist(
                    trackId,
                    musicStore.spotifyUserId,
                    playlistId
                );
            }, 5000);
        } else {
            // a device is found, play using the device
            await this.playSpotifyTrackFromPlaylist(
                trackId,
                musicStore.spotifyUserId,
                playlistId
            );
        }
    }

    async playSpotifyTrackFromPlaylist(
        trackId: string,
        spotifyUserId: string,
        playlistId: string = ""
    ) {
        const spotifyUserUri = musicUtil.createSpotifyUserUriFromId(
            spotifyUserId
        );
        if (playlistId === SPOTIFY_LIKED_SONGS_PLAYLIST_NAME) {
            playlistId = "";
        }
        const spotifyDevices: PlayerDevice[] = await this.getSpotifyDevices();
        const deviceId = spotifyDevices.length > 0 ? spotifyDevices[0].id : "";
        let options: any = {};
        if (deviceId) {
            options["device_id"] = deviceId;
        }

        if (trackId) {
            options["track_ids"] = [trackId];
        } else {
            options["offset"] = { position: 0 };
        }
        if (playlistId) {
            const playlistUri = `${spotifyUserUri}:playlist:${playlistId}`;
            options["context_uri"] = playlistUri;
        }

        /**
         * to play a track without the play list id
         * curl -X "PUT" "https://api.spotify.com/v1/me/player/play?device_id=4f38ae14f61b3a2e4ed97d537a5cb3d09cf34ea1"
         * --data "{\"uris\":[\"spotify:track:2j5hsQvApottzvTn4pFJWF\"]}"
         */

        if (!playlistId) {
            // just play by track id
            await musicController.spotifyWebPlayTrack(trackId, deviceId);
        } else {
            // we have playlist id within the options, use that
            await musicController.spotifyWebPlayPlaylist(
                playlistId,
                trackId,
                deviceId
            );
        }
    }

    launchWebPlayer(options: any) {
        if (options.album_id) {
            const albumId = musicUtil.createSpotifyIdFromUri(options.album_id);
            return musicUtil.launchWebUrl(
                `https://open.spotify.com/album/${albumId}`
            );
        } else if (options.track_id) {
            const trackId = musicUtil.createSpotifyIdFromUri(options.track_id);
            return musicUtil.launchWebUrl(
                `https://open.spotify.com/track/${trackId}`
            );
        } else if (options.playlist_id) {
            const playlistId = musicUtil.createSpotifyIdFromUri(
                options.playlist_id
            );
            return musicUtil.launchWebUrl(
                `https://open.spotify.com/playlist/${playlistId}`
            );
        }
        return musicUtil.launchWebUrl("https://open.spotify.com");
    }

    updateSpotifyLoved(loved: boolean) {
        //
    }
}
