import { generateRandomString, InnertubeError, throwIfMissing, u8ToBase64 } from '../../utils/Utils.js';
import { Album, Artist, Explore, HomeFeed, Library, Playlist, Recap, Search, TrackInfo } from '../../parser/ytmusic/index.js';
import AutomixPreviewVideo from '../../parser/classes/AutomixPreviewVideo.js';
import Message from '../../parser/classes/Message.js';
import MusicDescriptionShelf from '../../parser/classes/MusicDescriptionShelf.js';
import MusicQueue from '../../parser/classes/MusicQueue.js';
import MusicResponsiveListItem from '../../parser/classes/MusicResponsiveListItem.js';
import MusicTwoRowItem from '../../parser/classes/MusicTwoRowItem.js';
import NavigationEndpoint from '../../parser/classes/NavigationEndpoint.js';
import PlaylistPanel from '../../parser/classes/PlaylistPanel.js';
import SearchSuggestionsSection from '../../parser/classes/SearchSuggestionsSection.js';
import SectionList from '../../parser/classes/SectionList.js';
import Tab from '../../parser/classes/Tab.js';
import { SearchFilter } from '../../../protos/generated/misc/params.js';
export default class Music {
    #session;
    #actions;
    constructor(session) {
        this.#session = session;
        this.#actions = session.actions;
    }
    /**
     * Retrieves track info. Passing a list item of type MusicTwoRowItem automatically starts a radio.
     * @param target - Video id or a list item.
     * @param options - Options for fetching video info.
     */
    getInfo(target, options) {
        if (target instanceof MusicTwoRowItem) {
            return this.#fetchInfoFromEndpoint(target.endpoint, options);
        }
        else if (target instanceof MusicResponsiveListItem) {
            return this.#fetchInfoFromEndpoint(target.overlay?.content?.endpoint ?? target.endpoint, options);
        }
        else if (target instanceof NavigationEndpoint) {
            return this.#fetchInfoFromEndpoint(target, options);
        }
        return this.#fetchInfoFromVideoId(target, options);
    }
    async #fetchInfoFromVideoId(video_id, options) {
        const payload = { videoId: video_id, racyCheckOk: true, contentCheckOk: true };
        const watch_endpoint = new NavigationEndpoint({ watchEndpoint: payload });
        const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: payload });
        const extra_payload = {
            playbackContext: {
                contentPlaybackContext: {
                    vis: 0,
                    splay: false,
                    lactMilliseconds: '-1',
                    signatureTimestamp: this.#session.player?.signature_timestamp
                }
            },
            client: 'YTMUSIC'
        };
        if (options?.po_token) {
            extra_payload.serviceIntegrityDimensions = {
                poToken: options.po_token
            };
        }
        else if (this.#session.po_token) {
            extra_payload.serviceIntegrityDimensions = {
                poToken: this.#session.po_token
            };
        }
        const watch_response = watch_endpoint.call(this.#actions, extra_payload);
        const watch_next_response = watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        const response = await Promise.all([watch_response, watch_next_response]);
        const cpn = generateRandomString(16);
        return new TrackInfo(response, this.#actions, cpn);
    }
    async #fetchInfoFromEndpoint(endpoint, options) {
        if (!endpoint)
            throw new Error('This item does not have an endpoint.');
        const extra_payload = {
            playbackContext: {
                contentPlaybackContext: {
                    vis: 0,
                    splay: false,
                    lactMilliseconds: '-1',
                    signatureTimestamp: this.#session.player?.signature_timestamp
                }
            },
            client: 'YTMUSIC'
        };
        if (options?.po_token) {
            extra_payload.serviceIntegrityDimensions = {
                poToken: options.po_token
            };
        }
        else if (this.#session.po_token) {
            extra_payload.serviceIntegrityDimensions = {
                poToken: this.#session.po_token
            };
        }
        const player_response = endpoint.call(this.#actions, extra_payload);
        const next_response = endpoint.call(this.#actions, {
            client: 'YTMUSIC',
            enablePersistentPlaylistPanel: true,
            override_endpoint: '/next'
        });
        const cpn = generateRandomString(16);
        const response = await Promise.all([player_response, next_response]);
        return new TrackInfo(response, this.#actions, cpn);
    }
    async search(query, filters = {}) {
        throwIfMissing({ query });
        let params;
        if (filters.type && filters.type !== 'all') {
            const writer = SearchFilter.encode({
                filters: {
                    musicSearchType: {
                        [filters.type]: true
                    }
                }
            });
            params = encodeURIComponent(u8ToBase64(writer.finish()));
        }
        const search_endpoint = new NavigationEndpoint({ searchEndpoint: { query, params } });
        const response = await search_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Search(response, this.#actions, Reflect.has(filters, 'type') && filters.type !== 'all');
    }
    async getHomeFeed() {
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_home' } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new HomeFeed(response, this.#actions);
    }
    async getExplore() {
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_explore' } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Explore(response);
        // TODO: return new Explore(response, this.#actions);
    }
    async getLibrary() {
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_library_landing' } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Library(response, this.#actions);
    }
    async getArtist(artist_id) {
        if (!artist_id || !artist_id.startsWith('UC') && !artist_id.startsWith('FEmusic_library_privately_owned_artist'))
            throw new InnertubeError('Invalid artist id', artist_id);
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: artist_id } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Artist(response, this.#actions);
    }
    async getAlbum(album_id) {
        if (!album_id || !album_id.startsWith('MPR') && !album_id.startsWith('FEmusic_library_privately_owned_release'))
            throw new InnertubeError('Invalid album id', album_id);
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: album_id } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Album(response);
    }
    async getPlaylist(playlist_id) {
        if (!playlist_id.startsWith('VL'))
            playlist_id = `VL${playlist_id}`;
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: playlist_id } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Playlist(response, this.#actions);
    }
    async getUpNext(video_id, automix = true) {
        throwIfMissing({ video_id });
        const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
        const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
        const tabs = response.contents_memo?.getType(Tab);
        const tab = tabs?.[0];
        if (!tab)
            throw new InnertubeError('Could not find target tab.');
        const music_queue = tab.content?.as(MusicQueue);
        if (!music_queue || !music_queue.content)
            throw new InnertubeError('Music queue was empty, the given id is probably invalid.', music_queue);
        const playlist_panel = music_queue.content.as(PlaylistPanel);
        if (!playlist_panel.playlist_id && automix) {
            const automix_preview_video = playlist_panel.contents.firstOfType(AutomixPreviewVideo);
            if (!automix_preview_video)
                throw new InnertubeError('Automix item not found');
            const page = await automix_preview_video.playlist_video?.endpoint.call(this.#actions, {
                videoId: video_id,
                client: 'YTMUSIC',
                parse: true
            });
            if (!page || !page.contents_memo)
                throw new InnertubeError('Could not fetch automix');
            return page.contents_memo.getType(PlaylistPanel)[0];
        }
        return playlist_panel;
    }
    async getRelated(video_id) {
        throwIfMissing({ video_id });
        const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
        const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
        const tabs = response.contents_memo?.getType(Tab);
        const tab = tabs?.find((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_RELATED');
        if (!tab)
            throw new InnertubeError('Could not find target tab.');
        const page = await tab.endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
        if (!page.contents)
            throw new InnertubeError('Unexpected response', page);
        return page.contents.item().as(SectionList, Message);
    }
    async getLyrics(video_id) {
        throwIfMissing({ video_id });
        const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { videoId: video_id } });
        const response = await watch_next_endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
        const tabs = response.contents_memo?.getType(Tab);
        const tab = tabs?.find((tab) => tab.endpoint.payload.browseEndpointContextSupportedConfigs?.browseEndpointContextMusicConfig?.pageType === 'MUSIC_PAGE_TYPE_TRACK_LYRICS');
        if (!tab)
            throw new InnertubeError('Could not find target tab.');
        const page = await tab.endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
        if (!page.contents)
            throw new InnertubeError('Unexpected response', page);
        if (page.contents.item().type === 'Message')
            throw new InnertubeError(page.contents.item().as(Message).text.toString(), video_id);
        const section_list = page.contents.item().as(SectionList).contents;
        return section_list.firstOfType(MusicDescriptionShelf);
    }
    async getRecap() {
        const browse_endpoint = new NavigationEndpoint({ browseEndpoint: { browseId: 'FEmusic_listening_review' } });
        const response = await browse_endpoint.call(this.#actions, { client: 'YTMUSIC' });
        return new Recap(response, this.#actions);
    }
    async getSearchSuggestions(input) {
        const response = await this.#actions.execute('/music/get_search_suggestions', {
            input,
            client: 'YTMUSIC',
            parse: true
        });
        if (!response.contents_memo)
            return [];
        return response.contents_memo.getType(SearchSuggestionsSection);
    }
}
//# sourceMappingURL=Music.js.map