"use strict";
//
// Copyright 2022-2026 Picovoice Inc.
//
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
//
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const path = require("path");
const assert = require("assert");
const pv_status_t_1 = require("./pv_status_t");
const errors_1 = require("./errors");
const platforms_1 = require("./platforms");
const DEFAULT_MODEL_PATH = '../lib/common/cheetah_params.pv';
/**
 * Node.js binding for Cheetah streaming speech-to-text engine
 *
 * Performs the calls to the Cheetah node library. Does some basic parameter validation to prevent
 * errors occurring in the library layer. Provides clearer error messages in native JavaScript.
 */
class Cheetah {
    _pvCheetah;
    _handle;
    _version;
    _frameLength;
    _sampleRate;
    /**
     * Creates an instance of Cheetah.
     * @param {string} accessKey AccessKey obtained from Picovoice Console (https://console.picovoice.ai/).
     * @param options Optional configuration arguments.
     * @param {string} options.modelPath the path to the Cheetah model (.pv extension)
     * @param {string} options.device String representation of the device (e.g., CPU or GPU) to use.
     *   If set to `best`, the most suitable device is selected automatically.
     *   If set to `gpu`, the engine uses the first available GPU device.
     *   To select a specific GPU device, set this argument to `gpu:${GPU_INDEX}`.
     *   If set to `cpu`, the engine will run on the CPU with the default number of threads.
     *   To specify the number of threads, set this argument to `cpu:${NUM_THREADS}`.
     * @param {string} options.libraryPath the path to the Cheetah library (.node extension)
     * @param {number} options.endpointDurationSec Duration of endpoint in seconds. A speech endpoint is detected when there is a
     * chunk of audio (with a duration specified herein) after an utterance without any speech in it. Set to `0`
     * to disable endpoint detection.
     * @param {boolean} options.enableAutomaticPunctuation Flag to enable automatic punctuation insertion.
     * @param {boolean} options.enableTextNormalization Flag to enable text normalization. Enabling this feature
     * improves the readability and formatting of Cheetah's transcriptions (e.g. converts number words to digits)
     * at the cost of some additional latency.
     */
    constructor(accessKey, options = {}) {
        assert(typeof accessKey === 'string');
        if (accessKey === null ||
            accessKey === undefined ||
            accessKey.length === 0) {
            throw new errors_1.CheetahInvalidArgumentError(`No AccessKey provided to Cheetah`);
        }
        const { modelPath = path.resolve(__dirname, DEFAULT_MODEL_PATH), device = "best", libraryPath = (0, platforms_1.getSystemLibraryPath)(), endpointDurationSec = 1.0, enableAutomaticPunctuation = false, enableTextNormalization = false, } = options;
        if (endpointDurationSec < 0) {
            throw new RangeError(`Duration of endpoint value in 'endpointDurationSec' must be a positive number: ${endpointDurationSec}`);
        }
        if (!fs.existsSync(libraryPath)) {
            throw new errors_1.CheetahInvalidArgumentError(`File not found at 'libraryPath': ${libraryPath}`);
        }
        if (!fs.existsSync(modelPath)) {
            throw new errors_1.CheetahInvalidArgumentError(`File not found at 'modelPath': ${modelPath}`);
        }
        if (device === null ||
            device === undefined ||
            device.length === 0) {
            throw new errors_1.CheetahInvalidArgumentError(`No device provided to Cheetah`);
        }
        const pvCheetah = require(libraryPath); // eslint-disable-line
        this._pvCheetah = pvCheetah;
        let cheetahHandleAndStatus = null;
        try {
            pvCheetah.set_sdk('nodejs');
            cheetahHandleAndStatus = pvCheetah.init(accessKey, modelPath, device, endpointDurationSec, enableAutomaticPunctuation, enableTextNormalization);
        }
        catch (err) {
            (0, errors_1.pvStatusToException)(pv_status_t_1.default[err.code], err);
        }
        const status = cheetahHandleAndStatus.status;
        if (status !== pv_status_t_1.default.SUCCESS) {
            this.handlePvStatus(status, 'Cheetah failed to initialize');
        }
        this._handle = cheetahHandleAndStatus.handle;
        this._sampleRate = pvCheetah.sample_rate();
        this._frameLength = pvCheetah.frame_length();
        this._version = pvCheetah.version();
    }
    /**
     * @returns number of audio samples per frame (i.e. the length of the array provided to the process function)
     * @see {@link process}
     */
    get frameLength() {
        return this._frameLength;
    }
    /**
     * @returns the audio sampling rate accepted by the process function
     * @see {@link process}
     */
    get sampleRate() {
        return this._sampleRate;
    }
    /**
     * @returns the version of the Cheetah engine
     */
    get version() {
        return this._version;
    }
    /**
     * Lists all available devices that Cheetah can use for inference. Each entry in the list can be the `device` argument
     * of the constructor.
     *
     * @returns List of all available devices that Cheetah can use for inference.
     */
    static listAvailableDevices(options = {}) {
        const { libraryPath = (0, platforms_1.getSystemLibraryPath)(), } = options;
        const pvCheetah = require(libraryPath); // eslint-disable-line
        let cheetahHardwareDevicesAndStatus = null;
        try {
            cheetahHardwareDevicesAndStatus = pvCheetah.list_hardware_devices();
        }
        catch (err) {
            (0, errors_1.pvStatusToException)(err.code, err);
        }
        const status = cheetahHardwareDevicesAndStatus.status;
        if (status !== pv_status_t_1.default.SUCCESS) {
            const errorObject = pvCheetah.get_error_stack();
            if (errorObject.status === pv_status_t_1.default.SUCCESS) {
                (0, errors_1.pvStatusToException)(status, 'Cheetah failed to get available devices', errorObject.message_stack);
            }
            else {
                (0, errors_1.pvStatusToException)(status, 'Unable to get Cheetah error state');
            }
        }
        return cheetahHardwareDevicesAndStatus.hardware_devices;
    }
    /**
     * Processes a frame of audio and returns newly-transcribed text and a flag indicating if an endpoint has been detected.
     * Upon detection of an endpoint, the client may invoke `Cheetah.flush()` to retrieve any remaining transcription.
     *
     * @param {Int16Array} pcm Audio data. The audio needs to have a sample rate equal to `Cheetah.sampleRate` and be 16-bit linearly-encoded.
     * The specific array length can be attained by calling `Cheetah.frameLength`. This function operates on single-channel audio.
     * @returns {string, boolean} Inferred transcription, and a flag indicating if an endpoint has been detected.
     */
    process(pcm) {
        assert(pcm instanceof Int16Array);
        if (this._handle === 0 ||
            this._handle === null ||
            this._handle === undefined) {
            throw new errors_1.CheetahInvalidStateError('Cheetah is not initialized');
        }
        if (pcm === undefined || pcm === null) {
            throw new errors_1.CheetahInvalidArgumentError(`PCM array provided to 'Cheetah.process()' is undefined or null`);
        }
        else if (pcm.length !== this.frameLength) {
            throw new errors_1.CheetahInvalidArgumentError(`Size of frame array provided to 'Cheetah.process()' (${pcm.length}) does not match the engine 'Cheetah.frameLength' (${this.frameLength})`);
        }
        let partialTranscriptAndStatus = null;
        try {
            partialTranscriptAndStatus = this._pvCheetah.process(this._handle, pcm);
        }
        catch (err) {
            (0, errors_1.pvStatusToException)(pv_status_t_1.default[err.code], err);
        }
        const status = partialTranscriptAndStatus.status;
        if (status !== pv_status_t_1.default.SUCCESS) {
            this.handlePvStatus(status, 'Cheetah failed to process the audio frame');
        }
        return [
            partialTranscriptAndStatus.transcript,
            partialTranscriptAndStatus.is_endpoint !== 0,
        ];
    }
    /**
     * Marks the end of the audio stream, flushes internal state of the object, and returns any remaining transcript.
     *
     * @returns {string} Inferred transcription.
     */
    flush() {
        if (this._handle === 0 ||
            this._handle === null ||
            this._handle === undefined) {
            throw new errors_1.CheetahInvalidStateError('Cheetah is not initialized');
        }
        let transcriptAndStatus = null;
        try {
            transcriptAndStatus = this._pvCheetah.flush(this._handle);
        }
        catch (err) {
            (0, errors_1.pvStatusToException)(pv_status_t_1.default[err.code], err);
        }
        const status = transcriptAndStatus.status;
        if (status !== pv_status_t_1.default.SUCCESS) {
            this.handlePvStatus(status, 'Cheetah failed to flush');
        }
        return transcriptAndStatus.transcript;
    }
    /**
     * Releases the resources acquired by Cheetah.
     *
     * Be sure to call this when finished with the instance
     * to reclaim the memory that was allocated by the C library.
     */
    release() {
        if (this._handle !== 0) {
            try {
                this._pvCheetah.delete(this._handle);
            }
            catch (err) {
                (0, errors_1.pvStatusToException)(err.code, err);
            }
            this._handle = 0;
        }
        else {
            // eslint-disable-next-line no-console
            console.warn('Cheetah is not initialized');
        }
    }
    handlePvStatus(status, message) {
        const errorObject = this._pvCheetah.get_error_stack();
        if (errorObject.status === pv_status_t_1.default.SUCCESS) {
            (0, errors_1.pvStatusToException)(status, message, errorObject.message_stack);
        }
        else {
            (0, errors_1.pvStatusToException)(status, 'Unable to get Cheetah error state');
        }
    }
}
exports.default = Cheetah;
//# sourceMappingURL=cheetah.js.map