"use strict";
/*!
 * Copyright (c) 2026-present, Vanilagy and contributors
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ac3_1 = __importDefault(require("../build/ac3"));
let module;
let modulePromise = null;
let initDecoderFn;
let configureDecodePacket;
let decodePacket;
let getDecodedFormat;
let getDecodedPlanePtr;
let getDecodedChannels;
let getDecodedSampleRate;
let getDecodedSampleCount;
let getDecodedPts;
let flushDecoderFn;
let closeDecoderFn;
let initEncoderFn;
let getEncoderFrameSize;
let getEncodeInputPtr;
let encodeFrameFn;
let flushEncoderFn;
let getEncodedData;
let getEncodedPts;
let getEncodedDuration;
let closeEncoderFn;
const codecToId = (codec) => codec === 'ac3' ? 0 : 1;
const ensureModule = async () => {
    if (!module) {
        if (modulePromise) {
            // If we don't do this we can have a race condition
            return modulePromise;
        }
        modulePromise = (0, ac3_1.default)();
        module = await modulePromise;
        modulePromise = null;
        initDecoderFn = module.cwrap('init_decoder', 'number', ['number']);
        configureDecodePacket = module.cwrap('configure_decode_packet', 'number', ['number', 'number']);
        decodePacket = module.cwrap('decode_packet', 'number', ['number', 'number']);
        getDecodedFormat = module.cwrap('get_decoded_format', 'number', ['number']);
        getDecodedPlanePtr = module.cwrap('get_decoded_plane_ptr', 'number', ['number', 'number']);
        getDecodedChannels = module.cwrap('get_decoded_channels', 'number', ['number']);
        getDecodedSampleRate = module.cwrap('get_decoded_sample_rate', 'number', ['number']);
        getDecodedSampleCount = module.cwrap('get_decoded_sample_count', 'number', ['number']);
        getDecodedPts = module.cwrap('get_decoded_pts', 'number', ['number']);
        flushDecoderFn = module.cwrap('flush_decoder', null, ['number']);
        closeDecoderFn = module.cwrap('close_decoder', null, ['number']);
        initEncoderFn = module.cwrap('init_encoder', 'number', ['number', 'number', 'number', 'number']);
        getEncoderFrameSize = module.cwrap('get_encoder_frame_size', 'number', ['number']);
        getEncodeInputPtr = module.cwrap('get_encode_input_ptr', 'number', ['number', 'number']);
        encodeFrameFn = module.cwrap('encode_frame', 'number', ['number', 'number']);
        flushEncoderFn = module.cwrap('flush_encoder', null, ['number']);
        getEncodedData = module.cwrap('get_encoded_data', 'number', ['number']);
        getEncodedPts = module.cwrap('get_encoded_pts', 'number', ['number']);
        getEncodedDuration = module.cwrap('get_encoded_duration', 'number', ['number']);
        closeEncoderFn = module.cwrap('close_encoder', null, ['number']);
    }
};
const initDecoder = async (codec) => {
    await ensureModule();
    const ctx = initDecoderFn(codecToId(codec));
    if (ctx === 0) {
        throw new Error('Failed to initialize AC3 decoder.');
    }
    return { ctx, frameSize: 0 };
};
// Keys are AVSampleFormat enum values
const AV_FORMAT_MAP = {
    0: { format: 'u8', bytesPerSample: 1, planar: false },
    1: { format: 's16', bytesPerSample: 2, planar: false },
    2: { format: 's32', bytesPerSample: 4, planar: false },
    3: { format: 'f32', bytesPerSample: 4, planar: false },
    5: { format: 'u8-planar', bytesPerSample: 1, planar: true },
    6: { format: 's16-planar', bytesPerSample: 2, planar: true },
    7: { format: 's32-planar', bytesPerSample: 4, planar: true },
    8: { format: 'f32-planar', bytesPerSample: 4, planar: true },
};
const decode = (ctx, encodedData, timestamp) => {
    const bytes = new Uint8Array(encodedData);
    const dataPtr = configureDecodePacket(ctx, bytes.length);
    if (dataPtr === 0) {
        throw new Error('Failed to configure decode packet.');
    }
    module.HEAPU8.set(bytes, dataPtr);
    const ret = decodePacket(ctx, BigInt(timestamp));
    if (ret < 0) {
        throw new Error(`Decode failed with error code ${ret}.`);
    }
    const avFormat = getDecodedFormat(ctx);
    const info = AV_FORMAT_MAP[avFormat];
    if (!info) {
        throw new Error(`Unsupported AVSampleFormat: ${avFormat}`);
    }
    const channels = getDecodedChannels(ctx);
    const sampleRate = getDecodedSampleRate(ctx);
    const sampleCount = getDecodedSampleCount(ctx);
    const pts = Number(getDecodedPts(ctx));
    let pcmData;
    if (info.planar) {
        const planeSize = sampleCount * info.bytesPerSample;
        const buffer = new Uint8Array(planeSize * channels);
        for (let ch = 0; ch < channels; ch++) {
            const ptr = getDecodedPlanePtr(ctx, ch);
            buffer.set(module.HEAPU8.subarray(ptr, ptr + planeSize), ch * planeSize);
        }
        pcmData = buffer.buffer;
    }
    else {
        const totalSize = sampleCount * channels * info.bytesPerSample;
        const ptr = getDecodedPlanePtr(ctx, 0);
        pcmData = module.HEAPU8.slice(ptr, ptr + totalSize).buffer;
    }
    return { pcmData, format: info.format, channels, sampleRate, sampleCount, pts };
};
const initEncoder = async (codec, numberOfChannels, sampleRate, bitrate) => {
    await ensureModule();
    const ctx = initEncoderFn(codecToId(codec), numberOfChannels, sampleRate, bitrate);
    if (ctx === 0) {
        throw new Error('Failed to initialize AC3 encoder.');
    }
    return { ctx, frameSize: getEncoderFrameSize(ctx) };
};
const encode = (ctx, audioData, timestamp) => {
    const audioBytes = new Uint8Array(audioData);
    const inputPtr = getEncodeInputPtr(ctx, audioBytes.length);
    if (inputPtr === 0) {
        throw new Error('Failed to allocate encoder input buffer.');
    }
    module.HEAPU8.set(audioBytes, inputPtr);
    const bytesWritten = encodeFrameFn(ctx, BigInt(timestamp));
    if (bytesWritten < 0) {
        throw new Error(`Encode failed with error code ${bytesWritten}.`);
    }
    const ptr = getEncodedData(ctx);
    const encodedData = module.HEAPU8.slice(ptr, ptr + bytesWritten).buffer;
    const pts = Number(getEncodedPts(ctx));
    const duration = getEncodedDuration(ctx);
    return { encodedData, pts, duration };
};
const flushEncoder = (ctx) => {
    flushEncoderFn(ctx);
};
const onMessage = (data) => {
    const { id, command } = data;
    const handleCommand = async () => {
        try {
            let result;
            const transferables = [];
            switch (command.type) {
                case 'init-decoder':
                    {
                        const { ctx, frameSize } = await initDecoder(command.data.codec);
                        result = { type: command.type, ctx, frameSize };
                    }
                    ;
                    break;
                case 'decode':
                    {
                        const decoded = decode(command.data.ctx, command.data.encodedData, command.data.timestamp);
                        result = {
                            type: command.type,
                            pcmData: decoded.pcmData,
                            format: decoded.format,
                            channels: decoded.channels,
                            sampleRate: decoded.sampleRate,
                            sampleCount: decoded.sampleCount,
                            pts: decoded.pts,
                        };
                        transferables.push(decoded.pcmData);
                    }
                    ;
                    break;
                case 'flush-decoder':
                    {
                        flushDecoderFn(command.data.ctx);
                        result = { type: command.type };
                    }
                    ;
                    break;
                case 'close-decoder':
                    {
                        closeDecoderFn(command.data.ctx);
                        result = { type: command.type };
                    }
                    ;
                    break;
                case 'init-encoder':
                    {
                        const { ctx, frameSize } = await initEncoder(command.data.codec, command.data.numberOfChannels, command.data.sampleRate, command.data.bitrate);
                        result = { type: command.type, ctx, frameSize };
                    }
                    ;
                    break;
                case 'encode':
                    {
                        const encoded = encode(command.data.ctx, command.data.audioData, command.data.timestamp);
                        result = {
                            type: command.type,
                            encodedData: encoded.encodedData,
                            pts: encoded.pts,
                            duration: encoded.duration,
                        };
                        transferables.push(encoded.encodedData);
                    }
                    ;
                    break;
                case 'flush-encoder':
                    {
                        flushEncoder(command.data.ctx);
                        result = { type: command.type };
                    }
                    ;
                    break;
                case 'close-encoder':
                    {
                        closeEncoderFn(command.data.ctx);
                        result = { type: command.type };
                    }
                    ;
                    break;
            }
            const response = {
                id,
                success: true,
                data: result,
            };
            sendMessage(response, transferables);
        }
        catch (error) {
            const response = {
                id,
                success: false,
                error,
            };
            sendMessage(response);
        }
    };
    void handleCommand();
};
const sendMessage = (data, transferables) => {
    if (parentPort) {
        parentPort.postMessage(data, transferables ?? []);
    }
    else {
        self.postMessage(data, { transfer: transferables ?? [] });
    }
};
let parentPort = null;
if (typeof self === 'undefined') {
    const workerModule = 'worker_threads';
    // eslint-disable-next-line @stylistic/max-len
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-member-access
    parentPort = require(workerModule).parentPort;
}
if (parentPort) {
    parentPort.on('message', onMessage);
}
else {
    self.addEventListener('message', event => onMessage(event.data));
}
