import { SignatureGenerator } from './algorithm.js';
import { recognizeBytes } from 'shazamio-core';
import { default as fetch } from 'node-fetch';
import { s16LEToSamplesArray } from './utils.js';
import fs from 'fs';
import { readFileSync } from 'fs';
import { Request, ShazamURLS } from './requests.js';
import { convertfile, tomp3 } from './to_pcm.js';
const TIME_ZONE = 'Europe/Paris';
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    }).toUpperCase();
}
export class Endpoint {
    constructor(timezone) {
        this.timezone = timezone;
    }
    url() {
        return `${Endpoint.SCHEME}://${Endpoint.HOSTNAME}/discovery/v5/en/US/iphone/-/tag/${uuidv4()}/${uuidv4()}`;
    }
    params() {
        return {
            'sync': 'true',
            'webv3': 'true',
            'sampling': 'true',
            'connected': '',
            'shazamapiversion': 'v3',
            'sharehub': 'true',
            'hubv5minorversion': 'v5.1',
            'hidelb': 'true',
            'video': 'v3'
        };
    }
    headers(language = 'en') {
        return Request.headers(language);
    }
    async sendRecognizeRequest(url, body, language = 'en') {
        //@ts-ignore
        return await (await fetch(url, { body, headers: this.headers(language), method: 'POST' })).json();
    }
    async formatAndSendRecognizeRequest(signature, language = 'en') {
        const data = {
            'timezone': this.timezone,
            'signature': {
                'uri': signature.encodeToUri(),
                'samplems': Math.round(signature.numberSamples / signature.sampleRateHz * 1000)
            },
            'timestamp': new Date().getTime(),
            'context': {},
            'geolocation': {}
        };
        const url = new URL(this.url());
        Object.entries(this.params()).forEach(([a, b]) => url.searchParams.append(a, b));
        const response = await this.sendRecognizeRequest(url.toString(), JSON.stringify(data), language);
        if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
            return null;
        return response;
    }
}
Endpoint.SCHEME = 'https';
Endpoint.HOSTNAME = 'amp.shazam.com';
/**
 * @class Shazam
 */
export class Shazam {
    constructor(timeZone) {
        this.endpoint = new Endpoint(timeZone !== null && timeZone !== void 0 ? timeZone : TIME_ZONE);
    }
    headers(language = 'en') {
        return Request.headers(language);
    }
    /**
     * @deprecated
     * Recognise a song from an audio file
     * @param {string} path the path to the file
     * @param {Boolean} minimal false for full track data, true for simplified form
     * @param {string} language Song language
     * @returns {ShazamRoot | null}
     */
    async fromFilePath(path, minimal = false, language = 'en') {
        await convertfile(path);
        const data = fs.readFileSync('node_shazam_temp.pcm');
        const conv = s16LEToSamplesArray(data);
        fs.unlinkSync('node_shazam_temp.pcm');
        const recognise = minimal ? await this.recognizeSongMinimal(conv, language) : await this.recognizeSong(conv, language);
        return recognise;
    }
    /**
     * @deprecated
     * Recognise a song from a video file
     * @param {string} path the path to the file
     * @param {Boolean} minimal false for full track data, true for simplified form
     * @param {string} language Song language
     * @returns {ShazamRoot | null}
     */
    async fromVideoFile(path, minimal = false, language = 'en') {
        await tomp3(path);
        const res = await this.fromFilePath('node_shazam_temp.mp3', minimal, language);
        fs.unlinkSync('node_shazam_temp.mp3');
        return res;
    }
    /**
     * @deprecated
     * Recognise a song from Samples Array
     * @param {number[]} samples Samples array
     * @param {string} language  Song language
     */
    async recognizeSong(samples, language = 'en', callback) {
        const response = await this.fullRecognizeSong(samples, callback, language);
        if (!response)
            return null;
        return response;
    }
    /**
     * @deprecated
     * Recognise a song from Samples Array and return minial info
     * @param {number[]} samples Samples array
     * @param {string} language  Song language
     */
    async recognizeSongMinimal(samples, language = 'en', callback) {
        var _a, _b;
        const response = await this.fullRecognizeSong(samples, callback, language);
        if (!response)
            return null;
        const trackData = response.track, mainSection = trackData.sections.find((e) => e.type === 'SONG');
        const title = trackData.title, artist = trackData.subtitle, album = (_a = mainSection.metadata.find(e => e.title === 'Album')) === null || _a === void 0 ? void 0 : _a.text, year = (_b = mainSection.metadata.find(e => e.title === 'Released')) === null || _b === void 0 ? void 0 : _b.text;
        return { title, artist, album, year };
    }
    async fullRecognizeSong(samples, callback, language = 'en') {
        callback === null || callback === void 0 ? void 0 : callback('generating');
        const generator = this.createSignatureGenerator(samples);
        while (true) {
            callback === null || callback === void 0 ? void 0 : callback('generating');
            const signature = generator.getNextSignature();
            if (!signature) {
                break;
            }
            callback === null || callback === void 0 ? void 0 : callback('transmitting');
            const results = await this.endpoint.formatAndSendRecognizeRequest(signature, language);
            if (results !== null)
                return results;
        }
        return null;
    }
    /**
     * Recognise a song from a file
     * @param {string} path the path to the file
     * @param {string} language Song language
     * @param {boolean} minimal return minimal info
     * @returns {ShazamRoot | null}
     */
    async recognise(path, language = 'en-US', minimal = false) {
        var _a, _b;
        const signatures = recognizeBytes(readFileSync(path), 0, Number.MAX_SAFE_INTEGER);
        let response;
        for (let i = Math.floor(Math.random() * (signatures.length / 2)); i < signatures.length; i += 4) {
            const data = {
                'timezone': this.endpoint.timezone,
                'signature': {
                    'uri': signatures[i].uri,
                    'samplems': signatures[i].samplems
                },
                'timestamp': new Date().getTime(),
                'context': {},
                'geolocation': {}
            };
            const url = new URL(this.endpoint.url());
            Object.entries(this.endpoint.params()).forEach(([a, b]) => url.searchParams.append(a, b));
            response = await this.endpoint.sendRecognizeRequest(url.toString(), JSON.stringify(data), language);
            if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
                continue;
            break;
        }
        for (const sig of signatures)
            sig.free();
        if (!response)
            return null;
        if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
            return null;
        if (minimal) {
            const trackData = response.track, mainSection = trackData.sections.find((e) => e.type === 'SONG');
            const title = trackData.title, artist = trackData.subtitle, album = (_a = mainSection.metadata.find(e => e.title === 'Album')) === null || _a === void 0 ? void 0 : _a.text, year = (_b = mainSection.metadata.find(e => e.title === 'Released')) === null || _b === void 0 ? void 0 : _b.text;
            return { title, artist, album, year };
        }
        return response;
    }
    createSignatureGenerator(samples) {
        const signatureGenerator = new SignatureGenerator();
        signatureGenerator.feedInput(samples);
        return signatureGenerator;
    }
    /**
     * Most shazamed tracks globally
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async top_tracks_global(language = 'en-US', endpoint_country = 'GB', limit = '10', offset = '0') {
        const url = ShazamURLS.top_tracks_global(language, endpoint_country, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Most shazamed tracks for a country
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} country_code ISO country code for the country
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async top_tracks_country(language, endpoint_country, country_code, limit, offset) {
        const url = ShazamURLS.top_tracks_country(language, endpoint_country, country_code, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Most shazamed tracks for a city
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} city_id Shazam city id
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async top_tracks_city(language, endpoint_country, city_id, limit, offset) {
        const url = ShazamURLS.top_tracks_city(language, endpoint_country, city_id, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Info about a track
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} track_id Shazam track id
     */
    async track_info(language, endpoint_country, track_id) {
        const url = ShazamURLS.track_info(language, endpoint_country, track_id);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * List locations
     */
    async list_locations() {
        const url = ShazamURLS.locations();
        return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
    }
    /**
     * Most shazamed tracks globally for a genre
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} genre Genre to search
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async top_genre_tracks_world(language, endpoint_country, genre, limit, offset) {
        const url = ShazamURLS.genre_world(language, endpoint_country, genre, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Most shazamed tracks for a country
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} country ISO country code for the country
     * @param {string} genre Genre to search
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async top_genre_tracks_country(language, endpoint_country, country, genre, limit, offset) {
        const url = ShazamURLS.genre_country(language, endpoint_country, country, genre, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Related songs for a track
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} track_id Shazam track id
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async related_songs(language, endpoint_country, track_id, offset, limit) {
        const url = ShazamURLS.related_songs(language, endpoint_country, track_id, offset, limit);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Search artist by name
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} query Artist name
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async search_artist(language, endpoint_country, query, limit, offset) {
        const url = ShazamURLS.search_artist(language, endpoint_country, query, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Search artist by id
     * @param {string} endpoint_country Endpoint country
     * @param {string} artist_id Artist ID
     */
    async search_artist_v2(endpoint_country, artist_id) {
        const url = ShazamURLS.search_artist_v2(endpoint_country, artist_id);
        return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
    }
    /**
     * Albums by an artist
     * @param {string} endpoint_country Endpoint country
     * @param {string} artist_id Shazam artist id
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async artist_albums(endpoint_country, artist_id, limit, offset) {
        const url = ShazamURLS.artist_albums(endpoint_country, artist_id, limit, offset);
        return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
    }
    /**
     * Search music on shazam
     * @param {string} language  Song language
     * @param {string} endpoint_country Endpoint country
     * @param {string} query Query to search
     * @param {string} limit limit to how many tracks are fetched
     * @param {string} offset the offset to start fetching from
     */
    async search_music(language, endpoint_country, query, limit, offset) {
        const url = ShazamURLS.search_music(language, endpoint_country, query, limit, offset);
        return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
    }
    /**
     * Get number of times a track was shazamed
     * @param {string} track Track ID
     */
    async listen_count(track) {
        const url = ShazamURLS.listening_counter(track);
        return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
    }
}
Shazam.MAX_TIME_SCEONDS = 8;