"use strict";
/**
 * AudioEncoder - Encodes AudioData to encoded audio data
 * Implements the W3C WebCodecs AudioEncoder interface
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioEncoder = void 0;
const EncodedAudioChunk_1 = require("./EncodedAudioChunk");
const codec_registry_1 = require("./codec-registry");
const types_1 = require("./types");
// Load native addon
let native;
try {
    native = require('../build/Release/webcodecs_node.node');
}
catch {
    native = null;
}
class AudioEncoder {
    static async isConfigSupported(config) {
        const supported = (0, codec_registry_1.isAudioCodecSupported)(config.codec) &&
            config.sampleRate > 0 &&
            config.numberOfChannels > 0;
        return { supported, config };
    }
    constructor(init) {
        this._state = 'unconfigured';
        this._encodeQueueSize = 0;
        this._config = null;
        this._sentDecoderConfig = false;
        this._listeners = new Map();
        if (!init.output || typeof init.output !== 'function') {
            throw new TypeError('output callback is required');
        }
        if (!init.error || typeof init.error !== 'function') {
            throw new TypeError('error callback is required');
        }
        this._outputCallback = init.output;
        this._errorCallback = init.error;
        if (native) {
            this._native = new native.AudioEncoderNative(this._onChunk.bind(this), this._onError.bind(this));
        }
    }
    get state() {
        return this._state;
    }
    get encodeQueueSize() {
        return this._encodeQueueSize;
    }
    /**
     * Minimal EventTarget-style API for 'dequeue' events, mirroring VideoEncoder.
     */
    addEventListener(type, listener, options) {
        if (typeof listener !== 'function')
            return;
        const once = !!(options && options.once);
        const wrapper = once
            ? () => {
                this.removeEventListener(type, wrapper);
                listener();
            }
            : listener;
        let set = this._listeners.get(type);
        if (!set) {
            set = new Set();
            this._listeners.set(type, set);
        }
        set.add(wrapper);
    }
    removeEventListener(type, listener) {
        const set = this._listeners.get(type);
        if (!set)
            return;
        if (set.has(listener)) {
            set.delete(listener);
        }
        if (set.size === 0) {
            this._listeners.delete(type);
        }
    }
    _dispatchEvent(type) {
        const set = this._listeners.get(type);
        if (!set)
            return;
        for (const listener of Array.from(set)) {
            try {
                listener();
            }
            catch {
                // Swallow listener errors
            }
        }
    }
    configure(config) {
        if (this._state === 'closed') {
            throw new types_1.DOMException('Encoder is closed', 'InvalidStateError');
        }
        if (!(0, codec_registry_1.isAudioCodecSupported)(config.codec)) {
            throw new types_1.DOMException(`Unsupported codec: ${config.codec}`, 'NotSupportedError');
        }
        if (!native) {
            throw new types_1.DOMException('Native addon not available', 'NotSupportedError');
        }
        if (config.sampleRate <= 0 || config.numberOfChannels <= 0) {
            throw new types_1.DOMException('Invalid audio parameters', 'NotSupportedError');
        }
        const ffmpegCodec = (0, codec_registry_1.getFFmpegAudioCodec)(config.codec);
        const codecParams = {
            codec: ffmpegCodec,
            sampleRate: config.sampleRate,
            channels: config.numberOfChannels,
        };
        if (config.bitrate)
            codecParams.bitrate = config.bitrate;
        this._native.configure(codecParams);
        this._config = config;
        this._state = 'configured';
        this._sentDecoderConfig = false;
    }
    encode(data) {
        if (this._state !== 'configured') {
            throw new types_1.DOMException('Encoder is not configured', 'InvalidStateError');
        }
        // Get audio data buffer
        const bufferSize = data.allocationSize({ planeIndex: 0 });
        const buffer = new ArrayBuffer(bufferSize);
        data.copyTo(buffer, { planeIndex: 0 });
        this._encodeQueueSize++;
        this._native.encode(new Float32Array(buffer), data.format, data.sampleRate, data.numberOfFrames, data.numberOfChannels, data.timestamp);
    }
    async flush() {
        if (this._state !== 'configured') {
            throw new types_1.DOMException('Encoder is not configured', 'InvalidStateError');
        }
        return new Promise((resolve, reject) => {
            this._native.flush((err) => {
                if (err) {
                    reject(new types_1.DOMException(err.message, 'EncodingError'));
                }
                else {
                    resolve();
                }
            });
        });
    }
    reset() {
        if (this._state === 'closed') {
            throw new types_1.DOMException('Encoder is closed', 'InvalidStateError');
        }
        if (this._native) {
            this._native.reset();
        }
        this._encodeQueueSize = 0;
        this._state = 'unconfigured';
        this._sentDecoderConfig = false;
        this._config = null;
    }
    close() {
        if (this._state === 'closed')
            return;
        if (this._native) {
            this._native.close();
        }
        this._state = 'closed';
        this._encodeQueueSize = 0;
        this._config = null;
    }
    _onChunk(data, timestamp, duration, extradata) {
        this._encodeQueueSize = Math.max(0, this._encodeQueueSize - 1);
        this._dispatchEvent('dequeue');
        const chunk = new EncodedAudioChunk_1.EncodedAudioChunk({
            type: 'key', // Audio frames are typically all keyframes
            timestamp,
            duration: duration > 0 ? duration : undefined,
            data,
        });
        let metadata;
        if (!this._sentDecoderConfig && this._config) {
            metadata = {
                decoderConfig: {
                    codec: this._config.codec,
                    sampleRate: this._config.sampleRate,
                    numberOfChannels: this._config.numberOfChannels,
                    description: extradata ? new Uint8Array(extradata).buffer : undefined,
                },
            };
            this._sentDecoderConfig = true;
        }
        try {
            this._outputCallback(chunk, metadata);
        }
        catch (e) {
            // Don't propagate callback errors
        }
    }
    _onError(message) {
        try {
            this._errorCallback(new types_1.DOMException(message, 'EncodingError'));
        }
        catch (e) {
            // Don't propagate callback errors
        }
    }
}
exports.AudioEncoder = AudioEncoder;
//# sourceMappingURL=AudioEncoder.js.map