// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
import { fromMicros } from "@foxglove/rostime";
import { estimateObjectSize } from "@foxglove/studio-base/players/messageMemoryEstimation.js";
import { ensureWebCodecsSupported } from "../../../utils/compatibility.js";
import { DecoderRuntimeError } from "../../../utils/error.js";
import { Logger } from "../../../utils/logger";
import { parseMessage, parsePixelFormat } from "../../utils.js";
const logger = Logger.getLogger();
function configureDecoder(decoder) {
    decoder.configure({
        codec: "hev1.1.6.L93.B0",
        optimizeForLatency: true,
    });
}
function ensureDecoderConfigured(decoder) {
    if (decoder.state === "unconfigured") {
        configureDecoder(decoder);
    }
}
function closeDecoder(decoder) {
    if (!decoder) {
        return;
    }
    if (decoder.state === "closed") {
        return;
    }
    try {
        decoder.close();
    }
    catch (error) {
        logger.warn("Failed to close H265 decoder cleanly.", error);
    }
}
function closeTopicDecoder(topic, topicDecoders) {
    const decoderInfo = topicDecoders.get(topic);
    topicDecoders.delete(topic);
    closeDecoder(decoderInfo?.decoder);
    decoderInfo?.timing.clear();
    decoderInfo?.transformations.clear();
    if (decoderInfo) {
        decoderInfo.pendingFrame = undefined;
    }
}
function isUnconfiguredCodecError(error) {
    const message = error instanceof Error ? error.message : String(error);
    return /cannot call ["']decode["'] on an unconfigured (?:codec|decoder)/i.test(message);
}
function resetTopicDecoder({ topic, topicDecoders, callback, onError, }) {
    const existingDecoderInfo = topicDecoders.get(topic);
    closeDecoder(existingDecoderInfo?.decoder);
    const decoder = createVideoDecoder({ topic, callback, topicDecoders, onError });
    try {
        configureDecoder(decoder);
    }
    catch (error) {
        closeDecoder(decoder);
        closeTopicDecoder(topic, topicDecoders);
        onError(new DecoderRuntimeError(topic, error instanceof Error ? error.message : "Failed to configure H.265 decoder."));
        return undefined;
    }
    if (existingDecoderInfo) {
        existingDecoderInfo.decoder = decoder;
        existingDecoderInfo.timing.clear();
        existingDecoderInfo.transformations.clear();
        existingDecoderInfo.pendingFrame = undefined;
    }
    else {
        topicDecoders.set(topic, {
            decoder,
            timing: new Map(),
            transformations: new Map(),
            pendingFrame: undefined,
        });
    }
    return decoder;
}
function decodePendingFrame(topic, topicDecoders, callback, onError) {
    const decoderInfo = topicDecoders.get(topic);
    if (!decoderInfo) {
        return;
    }
    const decoder = decoderInfo.decoder;
    if (decoder.state === "closed" || decoder.decodeQueueSize > 0) {
        return;
    }
    const pendingFrame = decoderInfo.pendingFrame;
    if (!pendingFrame) {
        return;
    }
    decoderInfo.pendingFrame = undefined;
    decoderInfo.timing.set(pendingFrame.timestamp, pendingFrame.receiveTime);
    decoderInfo.transformations.set(pendingFrame.timestamp, pendingFrame.transformation);
    try {
        ensureDecoderConfigured(decoder);
        if (decoder.state !== "configured" || topicDecoders.get(topic)?.decoder !== decoder) {
            closeTopicDecoder(topic, topicDecoders);
            return;
        }
        const chunk = new EncodedVideoChunk({
            type: "key",
            data: pendingFrame.data,
            timestamp: pendingFrame.timestamp,
        });
        decoder.decode(chunk);
    }
    catch (error) {
        if (isUnconfiguredCodecError(error)) {
            decoderInfo.pendingFrame = pendingFrame;
            const replacementDecoder = resetTopicDecoder({ topic, topicDecoders, callback, onError });
            if (replacementDecoder?.state === "configured") {
                decodePendingFrame(topic, topicDecoders, callback, onError);
            }
            return;
        }
        closeTopicDecoder(topic, topicDecoders);
        onError(new DecoderRuntimeError(topic, error instanceof Error ? error.message : "Failed to decode H.265 frame."));
    }
}
function createVideoDecoder({ topic, callback, topicDecoders, onError, }) {
    let decoder;
    decoder = new VideoDecoder({
        output: async (frame) => {
            if (topicDecoders.get(topic)?.decoder !== decoder) {
                frame.close();
                return;
            }
            const buffer = new Uint8Array(frame.allocationSize());
            await frame.copyTo(buffer);
            const receiveTime = topicDecoders.get(topic)?.timing.get(frame.timestamp)
                ??
                    fromMicros(frame.timestamp);
            topicDecoders.get(topic)?.timing.delete(frame.timestamp);
            const transformation = topicDecoders.get(topic)?.transformations.get(frame.timestamp);
            topicDecoders.get(topic)?.transformations.delete(frame.timestamp);
            const [encoding, step] = parsePixelFormat(frame.format);
            const foxgloveMessage = {
                timestamp: receiveTime,
                frame_id: `h265-${topic}-frame`,
                width: frame.displayWidth,
                height: frame.displayHeight,
                data: buffer,
                encoding,
                step: frame.displayWidth * step
            };
            callback({
                topic,
                receiveTime,
                message: foxgloveMessage,
                sizeInBytes: estimateObjectSize(foxgloveMessage),
                schemaName: "foxglove.RawImage",
                depthaiTransformation: transformation,
            });
            frame.close();
            if (topicDecoders.get(topic)?.decoder === decoder) {
                decodePendingFrame(topic, topicDecoders, callback, onError);
            }
        },
        error: (error) => {
            if (topicDecoders.get(topic)?.decoder !== decoder) {
                return;
            }
            logger.error(error);
            closeTopicDecoder(topic, topicDecoders);
            onError(new DecoderRuntimeError(topic, error.message));
        },
    });
    return decoder;
}
export async function deserializeEncodedFrameH265({ topic, message, topicDecoders, callback, onError, }) {
    const { receiveTime } = parseMessage(message);
    const supported = ensureWebCodecsSupported("VideoDecoder");
    if (!supported) {
        return;
    }
    const existingDecoderInfo = topicDecoders.get(topic);
    if (!existingDecoderInfo || existingDecoderInfo.decoder.state !== "configured") {
        const decoder = resetTopicDecoder({ topic, callback, topicDecoders, onError });
        if (!decoder) {
            return;
        }
    }
    const decoderInfo = topicDecoders.get(topic);
    if (!decoderInfo) {
        return;
    }
    const microTimestamp = receiveTime.sec * 1_000_000 + Math.floor(receiveTime.nsec / 1_000);
    const frame = {
        data: message.data,
        timestamp: microTimestamp,
        receiveTime,
        transformation: message.transformation,
    };
    if (decoderInfo.decoder.decodeQueueSize > 0) {
        decoderInfo.pendingFrame = frame;
        return;
    }
    decoderInfo.pendingFrame = frame;
    decodePendingFrame(topic, topicDecoders, callback, onError);
}
//# sourceMappingURL=h265.js.map