"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TimelineKeyframeControls = exports.shouldShowTimelineKeyframeNavigation = exports.shouldShowTimelineKeyframeControls = exports.getSelectedKeyframeControlNodePathInfos = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const studio_shared_1 = require("@remotion/studio-shared");
const react_1 = require("react");
const remotion_1 = require("remotion");
const client_id_1 = require("../../helpers/client-id");
const colors_1 = require("../../helpers/colors");
const timeline_layout_1 = require("../../helpers/timeline-layout");
const timeline_node_path_key_1 = require("../../helpers/timeline-node-path-key");
const call_add_keyframe_1 = require("./call-add-keyframe");
const call_delete_keyframe_1 = require("./call-delete-keyframe");
const get_easing_selection_after_keyframe_delete_1 = require("./get-easing-selection-after-keyframe-delete");
const get_keyframe_navigation_1 = require("./get-keyframe-navigation");
const get_timeline_keyframes_1 = require("./get-timeline-keyframes");
const TimelineKeyframeDiamondIcon_1 = require("./TimelineKeyframeDiamondIcon");
const TimelineKeyframeTracksContext_1 = require("./TimelineKeyframeTracksContext");
const TimelineSelection_1 = require("./TimelineSelection");
const update_selected_easing_1 = require("./update-selected-easing");
const controlsContainerStyle = {
    alignItems: 'center',
    display: 'flex',
    flexShrink: 0,
    gap: 1,
    marginRight: 4,
};
const navButtonStyle = {
    alignItems: 'center',
    background: 'none',
    border: 'none',
    color: colors_1.WHITE,
    cursor: 'pointer',
    display: 'flex',
    flexShrink: 0,
    height: 14,
    justifyContent: 'center',
    lineHeight: 1,
    outline: 'none',
    padding: 0,
    userSelect: 'none',
    width: 14,
};
const isKeyframedStatus = (status) => {
    return status.status === 'keyframed';
};
const diamondButtonStyle = {
    ...navButtonStyle,
    background: 'none',
};
const svgStyle = { display: 'block' };
const isKeyframeControlSelection = (selection) => {
    return (selection.type === 'sequence-prop' ||
        selection.type === 'sequence-effect-prop');
};
const getSelectedKeyframeControlNodePathInfos = ({ clickedNodePathInfo, selectedItems, }) => {
    const clickedSelection = (0, TimelineSelection_1.getTimelineSelectionFromNodePathInfo)(clickedNodePathInfo);
    if (clickedSelection === null ||
        !isKeyframeControlSelection(clickedSelection)) {
        return [clickedNodePathInfo];
    }
    const clickedSelectionKey = (0, TimelineSelection_1.getTimelineSelectionKey)(clickedSelection);
    const selectedKeyframeControls = selectedItems.filter(isKeyframeControlSelection);
    const clickedIsSelected = selectedKeyframeControls.some((selection) => (0, TimelineSelection_1.getTimelineSelectionKey)(selection) === clickedSelectionKey);
    if (!clickedIsSelected || selectedKeyframeControls.length <= 1) {
        return [clickedNodePathInfo];
    }
    return selectedKeyframeControls.map((selection) => selection.nodePathInfo);
};
exports.getSelectedKeyframeControlNodePathInfos = getSelectedKeyframeControlNodePathInfos;
const findFieldNode = (nodes, nodePathInfoKey) => {
    for (const node of nodes) {
        if ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)(node.nodePathInfo) === nodePathInfoKey) {
            return node.kind === 'field' ? node : null;
        }
        if (node.kind === 'group') {
            const child = findFieldNode(node.children, nodePathInfoKey);
            if (child !== null) {
                return child;
            }
        }
    }
    return null;
};
const findTrackForNodePathInfo = ({ tracks, nodePathInfo, }) => {
    var _a;
    return ((_a = tracks.find((track) => {
        if (track.nodePathInfo === null) {
            return false;
        }
        return ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)({
            ...track.nodePathInfo,
            auxiliaryKeys: [],
        }) ===
            (0, timeline_node_path_key_1.timelineNodePathInfoToKey)({
                ...nodePathInfo,
                auxiliaryKeys: [],
            }));
    })) !== null && _a !== void 0 ? _a : null);
};
const resolveKeyframeControlTarget = ({ nodePathInfo, tracks, propStatuses, getDragOverrides, getEffectDragOverrides, timelinePosition, }) => {
    var _a;
    var _b, _c, _d;
    const track = findTrackForNodePathInfo({ tracks, nodePathInfo });
    if (track === null ||
        track.nodePathInfo === null ||
        !track.sequence.controls) {
        return null;
    }
    const tree = (0, timeline_layout_1.buildTimelineTree)({
        sequence: track.sequence,
        nodePathInfo: track.nodePathInfo,
        getDragOverrides,
        getEffectDragOverrides,
        propStatuses,
        includeTextContent: false,
        includeSourceControls: false,
    });
    const fieldNode = findFieldNode(tree, (0, timeline_node_path_key_1.timelineNodePathInfoToKey)(nodePathInfo));
    if (fieldNode === null || fieldNode.field === null) {
        return null;
    }
    const nodePath = nodePathInfo.sequenceSubscriptionKey;
    if (fieldNode.field.kind === 'sequence-field') {
        const sequencePropStatuses = remotion_1.Internals.getPropStatusesCtx(propStatuses, nodePath);
        const sequenceSelectedPropStatus = (_b = sequencePropStatuses === null || sequencePropStatuses === void 0 ? void 0 : sequencePropStatuses[fieldNode.field.key]) !== null && _b !== void 0 ? _b : null;
        if (sequenceSelectedPropStatus === null) {
            return null;
        }
        return {
            nodePathInfo,
            fieldKey: fieldNode.field.key,
            propStatus: sequenceSelectedPropStatus,
            nodePath,
            fileName: nodePath.absolutePath,
            keyframeDisplayOffset: track.keyframeDisplayOffset,
            sourceFrame: timelinePosition - track.keyframeDisplayOffset,
            defaultValue: fieldNode.field.fieldSchema.default,
            dragOverrideValue: ((_c = getDragOverrides(nodePath)) !== null && _c !== void 0 ? _c : {})[fieldNode.field.key],
            schema: track.sequence.controls.schema,
            effectIndex: null,
        };
    }
    const effectStatus = remotion_1.Internals.getEffectPropStatusesCtx({
        propStatuses,
        nodePath,
        effectIndex: fieldNode.field.effectIndex,
    });
    const effectSelectedPropStatus = effectStatus.type === 'can-update-effect'
        ? ((_d = (_a = effectStatus.props) === null || _a === void 0 ? void 0 : _a[fieldNode.field.key]) !== null && _d !== void 0 ? _d : null)
        : null;
    if (effectSelectedPropStatus === null) {
        return null;
    }
    return {
        nodePathInfo,
        fieldKey: fieldNode.field.key,
        propStatus: effectSelectedPropStatus,
        nodePath,
        fileName: nodePath.absolutePath,
        keyframeDisplayOffset: track.keyframeDisplayOffset,
        sourceFrame: timelinePosition - track.keyframeDisplayOffset,
        defaultValue: fieldNode.field.fieldSchema.default,
        dragOverrideValue: getEffectDragOverrides(nodePath, fieldNode.field.effectIndex)[fieldNode.field.key],
        schema: fieldNode.field.effectSchema,
        effectIndex: fieldNode.field.effectIndex,
    };
};
const hasTargetKeyframeAtCurrentFrame = (target) => {
    if (!isKeyframedStatus(target.propStatus)) {
        return false;
    }
    return (0, get_keyframe_navigation_1.hasKeyframeAtSourceFrame)(target.propStatus.keyframes, target.sourceFrame);
};
const getAddChange = (target) => {
    if (target.propStatus.status === 'computed' ||
        !(0, studio_shared_1.isSchemaFieldKeyframable)({ schema: target.schema, key: target.fieldKey }) ||
        hasTargetKeyframeAtCurrentFrame(target)) {
        return null;
    }
    const value = getCurrentKeyframeValue({
        propStatus: target.propStatus,
        jsxFrame: target.sourceFrame,
        defaultValue: target.defaultValue,
        dragOverrideValue: target.dragOverrideValue,
    });
    if (value === null) {
        return null;
    }
    const change = {
        fileName: target.fileName,
        nodePath: target.nodePath,
        fieldKey: target.fieldKey,
        sourceFrame: target.sourceFrame,
        value,
        schema: target.schema,
    };
    if (target.effectIndex === null) {
        return change;
    }
    return {
        ...change,
        effectIndex: target.effectIndex,
    };
};
const getDeleteChange = (target) => {
    if (!hasTargetKeyframeAtCurrentFrame(target)) {
        return null;
    }
    const change = {
        fileName: target.fileName,
        nodePath: target.nodePath,
        fieldKey: target.fieldKey,
        sourceFrame: target.sourceFrame,
        schema: target.schema,
        valueWhenLastKeyframeDeleted: null,
    };
    if (target.effectIndex === null) {
        return change;
    }
    return {
        ...change,
        effectIndex: target.effectIndex,
    };
};
const hasEffectIndex = (change) => {
    return 'effectIndex' in change;
};
function getCurrentKeyframeValue({ propStatus, jsxFrame, defaultValue, dragOverrideValue, }) {
    if (isKeyframedStatus(propStatus)) {
        return remotion_1.Internals.getEffectiveVisualModeValue({
            propStatus,
            dragOverrideValue,
            frame: jsxFrame,
            defaultValue,
            shouldResortToDefaultValueIfUndefined: true,
        });
    }
    if (propStatus.status === 'static') {
        return remotion_1.Internals.getEffectiveVisualModeValue({
            propStatus,
            dragOverrideValue,
            frame: jsxFrame,
            defaultValue,
            shouldResortToDefaultValueIfUndefined: true,
        });
    }
    return null;
}
const shouldShowTimelineKeyframeControls = ({ propStatus, selected, keyframable, }) => {
    if (propStatus === null) {
        return false;
    }
    if (!keyframable && !isKeyframedStatus(propStatus)) {
        return false;
    }
    if (selected) {
        return true;
    }
    return isKeyframedStatus(propStatus);
};
exports.shouldShowTimelineKeyframeControls = shouldShowTimelineKeyframeControls;
const shouldShowTimelineKeyframeNavigation = ({ propStatus, selected, }) => {
    if (selected) {
        return true;
    }
    return isKeyframedStatus(propStatus);
};
exports.shouldShowTimelineKeyframeNavigation = shouldShowTimelineKeyframeNavigation;
const TimelineKeyframeControls = ({ fieldKey, propStatus, nodePath, fileName, keyframeDisplayOffset, defaultValue, dragOverrideValue, schema, effectIndex, nodePathInfo, mode = 'timeline', }) => {
    const videoConfig = (0, remotion_1.useVideoConfig)();
    const timelinePosition = remotion_1.Internals.Timeline.useTimelinePosition();
    const setFrame = remotion_1.Internals.useTimelineSetFrame();
    const { setPropStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModeSettersContext);
    const { propStatuses } = (0, react_1.useContext)(remotion_1.Internals.VisualModePropStatusesContext);
    const { getDragOverrides, getEffectDragOverrides } = (0, react_1.useContext)(remotion_1.Internals.VisualModeDragOverridesContext);
    const { previewServerState } = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx);
    const { selectedItems, selectItems } = (0, TimelineSelection_1.useTimelineSelection)();
    const tracks = (0, TimelineKeyframeTracksContext_1.useTimelineKeyframeTracks)();
    const clientId = previewServerState.type === 'connected'
        ? previewServerState.clientId
        : null;
    const jsxFrame = timelinePosition - keyframeDisplayOffset;
    const keyframes = (0, react_1.useMemo)(() => (0, get_timeline_keyframes_1.getTimelineKeyframes)(propStatus, keyframeDisplayOffset), [propStatus, keyframeDisplayOffset]);
    const hasKeyframeAtCurrentFrame = (0, react_1.useMemo)(() => {
        if (!isKeyframedStatus(propStatus)) {
            return false;
        }
        return (0, get_keyframe_navigation_1.hasKeyframeAtSourceFrame)(propStatus.keyframes, jsxFrame);
    }, [jsxFrame, propStatus]);
    const previousDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getPreviousKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
    const nextDisplayFrame = (0, react_1.useMemo)(() => (0, get_keyframe_navigation_1.getNextKeyframeDisplayFrame)(keyframes, timelinePosition, videoConfig.durationInFrames), [keyframes, timelinePosition, videoConfig.durationInFrames]);
    const propertySelected = (0, react_1.useMemo)(() => {
        const propertySelection = (0, TimelineSelection_1.getTimelineSelectionFromNodePathInfo)(nodePathInfo);
        if (propertySelection === null) {
            return false;
        }
        const propertySelectionKey = (0, TimelineSelection_1.getTimelineSelectionKey)(propertySelection);
        return selectedItems.some((selection) => (0, TimelineSelection_1.getTimelineSelectionKey)(selection) === propertySelectionKey);
    }, [nodePathInfo, selectedItems]);
    const showNavigationButtons = (0, exports.shouldShowTimelineKeyframeNavigation)({
        propStatus,
        selected: propertySelected,
    });
    const keyframable = (0, studio_shared_1.isSchemaFieldKeyframable)({
        schema,
        key: fieldKey,
    });
    const canAddKeyframe = keyframable;
    const canToggleKeyframe = propStatus.status !== 'computed' &&
        (hasKeyframeAtCurrentFrame || canAddKeyframe);
    const selectedNodePathInfos = (0, react_1.useMemo)(() => (0, exports.getSelectedKeyframeControlNodePathInfos)({
        clickedNodePathInfo: nodePathInfo,
        selectedItems,
    }), [nodePathInfo, selectedItems]);
    const clickedTarget = (0, react_1.useMemo)(() => ({
        nodePathInfo,
        fieldKey,
        propStatus,
        nodePath,
        fileName,
        keyframeDisplayOffset,
        sourceFrame: jsxFrame,
        defaultValue,
        dragOverrideValue,
        schema,
        effectIndex,
    }), [
        defaultValue,
        dragOverrideValue,
        effectIndex,
        fieldKey,
        fileName,
        jsxFrame,
        keyframeDisplayOffset,
        nodePath,
        nodePathInfo,
        propStatus,
        schema,
    ]);
    const keyframeToggleTargets = (0, react_1.useMemo)(() => {
        const clickedNodePathInfoKey = (0, timeline_node_path_key_1.timelineNodePathInfoToKey)(nodePathInfo);
        return selectedNodePathInfos.flatMap((selectedNodePathInfo) => {
            if ((0, timeline_node_path_key_1.timelineNodePathInfoToKey)(selectedNodePathInfo) ===
                clickedNodePathInfoKey) {
                return [clickedTarget];
            }
            const target = resolveKeyframeControlTarget({
                nodePathInfo: selectedNodePathInfo,
                tracks,
                propStatuses,
                getDragOverrides,
                getEffectDragOverrides,
                timelinePosition,
            });
            return target === null ? [] : [target];
        });
    }, [
        clickedTarget,
        getDragOverrides,
        getEffectDragOverrides,
        nodePathInfo,
        propStatuses,
        selectedNodePathInfos,
        timelinePosition,
        tracks,
    ]);
    const seekToDisplayFrame = (0, react_1.useCallback)((frame) => {
        setFrame((current) => {
            const next = { ...current, [videoConfig.id]: frame };
            remotion_1.Internals.persistCurrentFrame(next);
            return next;
        });
    }, [setFrame, videoConfig.id]);
    const onPrevious = (0, react_1.useCallback)((e) => {
        e.stopPropagation();
        if (previousDisplayFrame !== null) {
            seekToDisplayFrame(previousDisplayFrame);
        }
    }, [previousDisplayFrame, seekToDisplayFrame]);
    const onNext = (0, react_1.useCallback)((e) => {
        e.stopPropagation();
        if (nextDisplayFrame !== null) {
            seekToDisplayFrame(nextDisplayFrame);
        }
    }, [nextDisplayFrame, seekToDisplayFrame]);
    const onToggleKeyframe = (0, react_1.useCallback)(async (e) => {
        e.stopPropagation();
        if (!clientId || !canToggleKeyframe) {
            return;
        }
        if (hasKeyframeAtCurrentFrame) {
            const deleteTargets = keyframeToggleTargets.flatMap((target) => {
                const change = getDeleteChange(target);
                return change === null ? [] : [{ target, change }];
            });
            const singleDeleteTarget = deleteTargets[0];
            const easingSelection = deleteTargets.length === 1 &&
                singleDeleteTarget &&
                isKeyframedStatus(singleDeleteTarget.target.propStatus) &&
                (0, update_selected_easing_1.canEditEasingForInterpolationFunction)(singleDeleteTarget.target.propStatus.interpolationFunction)
                ? (0, get_easing_selection_after_keyframe_delete_1.getEasingSelectionAfterKeyframeDelete)({
                    deletedSourceFrames: [singleDeleteTarget.target.sourceFrame],
                    keyframeDisplayOffset: singleDeleteTarget.target.keyframeDisplayOffset,
                    nodePathInfo: singleDeleteTarget.target.nodePathInfo,
                    propStatus: singleDeleteTarget.target.propStatus,
                    timelinePosition,
                })
                : null;
            const deleteChanges = deleteTargets.map(({ change }) => change);
            await (0, call_delete_keyframe_1.callDeleteKeyframes)({
                sequenceKeyframes: deleteChanges.filter((change) => !hasEffectIndex(change)),
                effectKeyframes: deleteChanges.filter((change) => hasEffectIndex(change)),
                setPropStatuses,
                clientId,
            });
            if (mode === 'timeline' && easingSelection !== null) {
                selectItems([easingSelection], { reveal: true });
            }
            return;
        }
        const addChanges = keyframeToggleTargets.flatMap((target) => {
            const change = getAddChange(target);
            return change === null ? [] : [{ target, change }];
        });
        if (addChanges.length === 0) {
            return;
        }
        const addChangeValues = addChanges.map(({ change }) => change);
        await (0, call_add_keyframe_1.callAddKeyframes)({
            sequenceKeyframes: addChangeValues.filter((change) => !hasEffectIndex(change)),
            effectKeyframes: addChangeValues.filter((change) => hasEffectIndex(change)),
            setPropStatuses,
            clientId,
        });
        if (mode === 'timeline') {
            selectItems(addChanges.map(({ target }) => ({
                type: 'keyframe',
                nodePathInfo: target.nodePathInfo,
                frame: target.sourceFrame + target.keyframeDisplayOffset,
            })), { reveal: true });
        }
    }, [
        canToggleKeyframe,
        clientId,
        hasKeyframeAtCurrentFrame,
        keyframeToggleTargets,
        mode,
        selectItems,
        setPropStatuses,
        timelinePosition,
    ]);
    const previousDisabled = previousDisplayFrame === null;
    const nextDisabled = nextDisplayFrame === null;
    const previousStyle = (0, react_1.useMemo)(() => ({
        ...navButtonStyle,
        cursor: previousDisabled ? 'default' : 'pointer',
        opacity: previousDisabled ? 0.35 : 1,
        visibility: showNavigationButtons ? 'visible' : 'hidden',
    }), [previousDisabled, showNavigationButtons]);
    const nextStyle = (0, react_1.useMemo)(() => ({
        ...navButtonStyle,
        cursor: nextDisabled ? 'default' : 'pointer',
        opacity: nextDisabled ? 0.35 : 1,
        visibility: showNavigationButtons ? 'visible' : 'hidden',
    }), [nextDisabled, showNavigationButtons]);
    const diamondStyle = (0, react_1.useMemo)(() => ({
        ...diamondButtonStyle,
        cursor: canToggleKeyframe && clientId ? 'pointer' : 'default',
        opacity: canToggleKeyframe && clientId ? 1 : 0.35,
    }), [canToggleKeyframe, clientId]);
    const diamondColor = hasKeyframeAtCurrentFrame ? colors_1.BLUE : colors_1.LIGHT_TEXT;
    return (jsx_runtime_1.jsxs("div", { style: controlsContainerStyle, children: [
            jsx_runtime_1.jsx("button", { type: "button", style: previousStyle, disabled: previousDisabled, onPointerDown: previousDisabled ? undefined : onPrevious, "aria-label": "Go to previous keyframe", title: "Previous keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M7 1.5L3 5L7 8.5Z", fill: colors_1.LIGHT_GRAY }) }) }), jsx_runtime_1.jsx("button", { type: "button", style: diamondStyle, disabled: !canToggleKeyframe || !clientId, onPointerDown: canToggleKeyframe && clientId ? onToggleKeyframe : undefined, "aria-label": hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', title: hasKeyframeAtCurrentFrame ? 'Remove keyframe' : 'Add keyframe', children: jsx_runtime_1.jsx(TimelineKeyframeDiamondIcon_1.TimelineKeyframeDiamondIcon, { color: diamondColor, size: 12 }) }), jsx_runtime_1.jsx("button", { type: "button", style: nextStyle, disabled: nextDisabled, onPointerDown: nextDisabled ? undefined : onNext, "aria-label": "Go to next keyframe", title: "Next keyframe", children: jsx_runtime_1.jsx("svg", { width: "14", height: "14", viewBox: "0 0 10 10", style: svgStyle, children: jsx_runtime_1.jsx("path", { d: "M3 1.5L7 5L3 8.5Z", fill: colors_1.LIGHT_GRAY }) }) })
        ] }));
};
exports.TimelineKeyframeControls = TimelineKeyframeControls;
