// 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 } from "../../utils.js";
import { copyVideoFrameToI420 } from "./h264.js";
const logger = Logger.getLogger();
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);
    closeDecoder(decoderInfo?.decoder);
    decoderInfo?.timing.clear();
    decoderInfo?.transformations.clear();
}
function createVideoDecoder({ topic, callback, topicDecoders, onError, }) {
    return new VideoDecoder({
        output: async (frame) => {
            const { data: buffer, width, height } = await copyVideoFrameToI420(frame, "H265");
            const receiveTime = topicDecoders.get(topic)?.timing.get(frame.timestamp)
                ??
                    fromMicros(frame.timestamp);
            const transformation = topicDecoders.get(topic)?.transformations.get(frame.timestamp);
            topicDecoders.get(topic)?.transformations.delete(frame.timestamp);
            const foxgloveMessage = {
                timestamp: receiveTime,
                frame_id: `h265-${topic}-frame`,
                width,
                height,
                data: buffer,
                encoding: "yuv420p",
                step: width,
            };
            callback({
                topic,
                receiveTime,
                message: foxgloveMessage,
                sizeInBytes: estimateObjectSize(foxgloveMessage),
                schemaName: "foxglove.RawImage",
                depthaiTransformation: transformation,
            });
            frame.close();
        },
        error: (error) => {
            logger.error(error);
            closeTopicDecoder(topic, topicDecoders);
            onError(new DecoderRuntimeError(topic, error.message));
        },
    });
}
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 === "closed") {
        const decoder = createVideoDecoder({ topic, callback, topicDecoders, onError });
        try {
            decoder.configure({
                codec: "hev1.1.6.L93.B0",
                optimizeForLatency: true
            });
        }
        catch (error) {
            closeDecoder(decoder);
            onError(new DecoderRuntimeError(topic, error instanceof Error ? error.message : "Failed to configure H.265 decoder."));
            return;
        }
        if (existingDecoderInfo) {
            existingDecoderInfo.decoder = decoder;
            existingDecoderInfo.timing.clear();
            existingDecoderInfo.transformations.clear();
        }
        else {
            topicDecoders.set(topic, {
                decoder,
                timing: new Map(),
                transformations: new Map(),
            });
        }
    }
    const decoderInfo = topicDecoders.get(topic);
    if (!decoderInfo) {
        return;
    }
    if (decoderInfo.decoder.decodeQueueSize > 60) {
        decoderInfo.decoder.reset();
        return;
    }
    const microTimestamp = receiveTime.sec * 1_000_000 + Math.floor(receiveTime.nsec / 1_000);
    decoderInfo.timing.set(microTimestamp, receiveTime);
    decoderInfo.transformations.set(microTimestamp, message.transformation);
    const frame = {
        type: 'key',
        data: message.data,
        timestamp: microTimestamp
    };
    try {
        const chunk = new EncodedVideoChunk(frame);
        decoderInfo.decoder.decode(chunk);
    }
    catch (error) {
        closeTopicDecoder(topic, topicDecoders);
        onError(new DecoderRuntimeError(topic, error instanceof Error ? error.message : "Failed to decode H.265 frame."));
    }
}
//# sourceMappingURL=h265.js.map