"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineVideoInfo = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const timeline_utils_1 = require("@remotion/timeline-utils");
const react_1 = require("react");
const remotion_1 = require("remotion");
const colors_1 = require("../../helpers/colors");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const AudioWaveform_1 = require("../AudioWaveform");
const get_timeline_media_start_frame_1 = require("./get-timeline-media-start-frame");
const get_timeline_video_info_widths_1 = require("./get-timeline-video-info-widths");
const timeline_video_filmstrip_times_1 = require("./timeline-video-filmstrip-times");
const outerStyle = {
    width: '100%',
    height: '100%',
    display: 'flex',
    flexDirection: 'column',
};
const filmstripContainerStyle = {
    height: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
    backgroundColor: colors_1.BLACK_ALPHA_30,
    display: 'flex',
    borderTopLeftRadius: 2,
    fontSize: 10,
    fontFamily: 'Arial, Helvetica',
};
const MAX_FROZEN_FRAME_CACHE_DEVIATION = timeline_utils_1.WEBCODECS_TIMESCALE * 0.05;
const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, startMediaFrom, mediaFrameAtSequenceZero, sequenceFrameOffset, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, loopDisplay, frozenMediaFrame, }) => {
    const { fps } = (0, remotion_1.useVideoConfig)();
    const ref = (0, react_1.useRef)(null);
    const [error, setError] = (0, react_1.useState)(null);
    const aspectRatio = (0, react_1.useRef)((0, timeline_utils_1.getAspectRatioFromCache)(src));
    const mediaStartFrame = (0, get_timeline_media_start_frame_1.getTimelineMediaStartFrame)({
        startMediaFrom,
        mediaFrameAtSequenceZero,
        sequenceFrameOffset,
        playbackRate,
    });
    const { mediaVisualizationWidth, mediaNaturalWidth } = (0, react_1.useMemo)(() => {
        return (0, get_timeline_video_info_widths_1.getTimelineVideoInfoWidths)({
            visualizationWidth,
            naturalWidth,
            premountWidth,
            postmountWidth,
        });
    }, [naturalWidth, postmountWidth, premountWidth, visualizationWidth]);
    // for rendering frames
    (0, react_1.useEffect)(() => {
        if (error) {
            return;
        }
        const { current } = ref;
        if (!current) {
            return;
        }
        const controller = new AbortController();
        const canvas = document.createElement('canvas');
        canvas.width = mediaVisualizationWidth;
        canvas.height = timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT;
        const ctx = canvas.getContext('2d');
        if (!ctx) {
            return;
        }
        current.appendChild(canvas);
        const drawRepeatedFrame = (frame) => {
            const thumbnailWidth = Math.max(1, frame.displayWidth / window.devicePixelRatio);
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            for (let x = 0; x < canvas.width; x += thumbnailWidth) {
                ctx.drawImage(frame, x, 0, thumbnailWidth, timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT);
            }
        };
        const getCachedFrozenFrame = (timestamp) => {
            const prefix = (0, timeline_utils_1.getFrameDatabaseKeyPrefix)(src);
            const keys = Array.from(timeline_utils_1.frameDatabase.keys()).filter((k) => k.startsWith(prefix));
            let bestDistance = Infinity;
            let bestFrame = null;
            for (const key of keys) {
                const frame = timeline_utils_1.frameDatabase.get(key);
                if (!frame) {
                    continue;
                }
                const distance = Math.abs((0, timeline_utils_1.getTimestampFromFrameDatabaseKey)(key) - timestamp);
                if (distance < bestDistance) {
                    bestDistance = distance;
                    bestFrame = frame.frame;
                }
            }
            return bestDistance <= MAX_FROZEN_FRAME_CACHE_DEVIATION
                ? bestFrame
                : null;
        };
        const times = (0, timeline_video_filmstrip_times_1.getTimelineVideoFilmstripTimes)({
            trimBefore: mediaStartFrame,
            durationInFrames,
            playbackRate,
            fps,
            loopDisplay,
            frozenMediaFrame,
        });
        if (times.type === 'frozen') {
            const timestamp = times.timestampInSeconds * timeline_utils_1.WEBCODECS_TIMESCALE;
            const cachedFrame = getCachedFrozenFrame(timestamp);
            if (cachedFrame) {
                drawRepeatedFrame(cachedFrame);
                return () => {
                    current.removeChild(canvas);
                };
            }
            (0, timeline_utils_1.extractFrames)({
                timestampsInSeconds: ({ track, }) => {
                    aspectRatio.current = track.width / track.height;
                    timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
                    return [times.timestampInSeconds];
                },
                src,
                onVideoSample: (sample) => {
                    let frame;
                    try {
                        frame = sample.toVideoFrame();
                        const scale = (timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT / frame.displayHeight) *
                            window.devicePixelRatio;
                        const transformed = (0, timeline_utils_1.resizeVideoFrame)({
                            frame,
                            scale,
                        });
                        if (transformed !== frame) {
                            frame.close();
                        }
                        frame = undefined;
                        const databaseKey = (0, timeline_utils_1.makeFrameDatabaseKey)(src, transformed.timestamp);
                        (0, timeline_utils_1.addFrameToCache)(databaseKey, transformed);
                        drawRepeatedFrame(transformed);
                    }
                    catch (e) {
                        if (frame) {
                            frame.close();
                        }
                        throw e;
                    }
                    finally {
                        sample.close();
                    }
                },
                signal: controller.signal,
            }).catch((e) => {
                setError(e);
            });
            return () => {
                controller.abort();
                current.removeChild(canvas);
            };
        }
        const loopWidth = (0, timeline_utils_1.getLoopDisplayWidth)({
            visualizationWidth: mediaNaturalWidth,
            loopDisplay,
        });
        const shouldRepeatVideo = (0, timeline_utils_1.shouldTileLoopDisplay)(loopDisplay);
        const targetCanvas = shouldRepeatVideo
            ? document.createElement('canvas')
            : canvas;
        targetCanvas.width = shouldRepeatVideo
            ? Math.max(1, Math.ceil(loopWidth))
            : canvas.width;
        targetCanvas.height = canvas.height;
        const targetCtx = shouldRepeatVideo ? targetCanvas.getContext('2d') : ctx;
        if (!targetCtx) {
            current.removeChild(canvas);
            return;
        }
        const repeatTarget = () => {
            if (!shouldRepeatVideo) {
                return;
            }
            const pattern = ctx.createPattern(targetCanvas, 'repeat-x');
            if (!pattern) {
                return;
            }
            pattern.setTransform(new DOMMatrix().scaleSelf(loopWidth / targetCanvas.width, 1));
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = pattern;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        };
        // desired-timestamp -> filled-timestamp
        const filledSlots = new Map();
        const { fromSeconds, toSeconds } = times;
        const targetWidth = shouldRepeatVideo
            ? targetCanvas.width
            : mediaNaturalWidth;
        if (aspectRatio.current !== null) {
            (0, timeline_utils_1.ensureSlots)({
                filledSlots,
                naturalWidth: targetWidth,
                fromSeconds,
                toSeconds,
                aspectRatio: aspectRatio.current,
                frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
            });
            (0, timeline_utils_1.fillWithCachedFrames)({
                ctx: targetCtx,
                naturalWidth: targetWidth,
                filledSlots,
                src,
                segmentDuration: toSeconds - fromSeconds,
                fromSeconds,
                devicePixelRatio: window.devicePixelRatio,
                frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
            });
            repeatTarget();
            const unfilled = Array.from(filledSlots.keys()).filter((timestamp) => !filledSlots.get(timestamp));
            // Don't extract frames if all slots are filled
            if (unfilled.length === 0) {
                return () => {
                    current.removeChild(canvas);
                };
            }
        }
        (0, timeline_utils_1.extractFrames)({
            timestampsInSeconds: ({ track, }) => {
                aspectRatio.current = track.width / track.height;
                timeline_utils_1.aspectRatioCache.set(src, aspectRatio.current);
                (0, timeline_utils_1.ensureSlots)({
                    filledSlots,
                    fromSeconds,
                    toSeconds,
                    naturalWidth: targetWidth,
                    aspectRatio: aspectRatio.current,
                    frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
                });
                return Array.from(filledSlots.keys()).map((timestamp) => timestamp / timeline_utils_1.WEBCODECS_TIMESCALE);
            },
            src,
            onVideoSample: (sample) => {
                let frame;
                try {
                    frame = sample.toVideoFrame();
                    const scale = (timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT / frame.displayHeight) *
                        window.devicePixelRatio;
                    const transformed = (0, timeline_utils_1.resizeVideoFrame)({
                        frame,
                        scale,
                    });
                    if (transformed !== frame) {
                        frame.close();
                    }
                    frame = undefined;
                    const databaseKey = (0, timeline_utils_1.makeFrameDatabaseKey)(src, transformed.timestamp);
                    (0, timeline_utils_1.addFrameToCache)(databaseKey, transformed);
                    if (aspectRatio.current === null) {
                        throw new Error('Aspect ratio is not set');
                    }
                    (0, timeline_utils_1.ensureSlots)({
                        filledSlots,
                        fromSeconds,
                        toSeconds,
                        naturalWidth: targetWidth,
                        aspectRatio: aspectRatio.current,
                        frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
                    });
                    (0, timeline_utils_1.fillFrameWhereItFits)({
                        ctx: targetCtx,
                        filledSlots,
                        visualizationWidth: targetWidth,
                        frame: transformed,
                        segmentDuration: toSeconds - fromSeconds,
                        fromSeconds,
                        devicePixelRatio: window.devicePixelRatio,
                        frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
                    });
                    repeatTarget();
                }
                catch (e) {
                    if (frame) {
                        frame.close();
                    }
                    throw e;
                }
                finally {
                    sample.close();
                }
            },
            signal: controller.signal,
        })
            .then(() => {
            if (controller.signal.aborted) {
                return;
            }
            (0, timeline_utils_1.fillWithCachedFrames)({
                ctx: targetCtx,
                naturalWidth: targetWidth,
                filledSlots,
                src,
                segmentDuration: toSeconds - fromSeconds,
                fromSeconds,
                devicePixelRatio: window.devicePixelRatio,
                frameHeight: timeline_layout_1.TIMELINE_LAYER_FILMSTRIP_HEIGHT,
            });
            repeatTarget();
        })
            .catch((e) => {
            setError(e);
        });
        return () => {
            controller.abort();
            current.removeChild(canvas);
        };
    }, [
        durationInFrames,
        error,
        fps,
        frozenMediaFrame,
        loopDisplay,
        mediaNaturalWidth,
        mediaVisualizationWidth,
        mediaStartFrame,
        playbackRate,
        src,
    ]);
    const audioWidth = mediaVisualizationWidth;
    const filmstripStyle = (0, react_1.useMemo)(() => {
        return {
            ...filmstripContainerStyle,
            width: mediaVisualizationWidth,
            marginLeft: premountWidth,
        };
    }, [mediaVisualizationWidth, premountWidth]);
    const audioStyle = (0, react_1.useMemo)(() => {
        return {
            width: audioWidth,
            position: 'relative',
            marginLeft: premountWidth,
        };
    }, [audioWidth, premountWidth]);
    return (jsx_runtime_1.jsxs("div", { style: outerStyle, children: [
            jsx_runtime_1.jsx("div", { ref: ref, style: filmstripStyle }), jsx_runtime_1.jsx("div", { style: audioStyle, children: jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: src, height: timeline_layout_1.TIMELINE_VIDEO_INFO_WAVEFORM_HEIGHT, visualizationWidth: audioWidth, startFrom: mediaStartFrame, durationInFrames: durationInFrames, volume: volume, doesVolumeChange: doesVolumeChange, playbackRate: playbackRate, loopDisplay: loopDisplay }) })
        ] }));
};
exports.TimelineVideoInfo = TimelineVideoInfo;
