"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.insertElement = exports.insertComposition = exports.insertComponent = exports.insertExistingAssets = exports.insertRemoteAudio = exports.importRemoteAsset = exports.importAssets = exports.pickFilesToImport = exports.getComponentDimensions = exports.getAssetPositionForDrop = exports.getCompositionPositionForDrop = exports.getElementPositionForDrop = exports.getAssetElementForDroppedFile = exports.getAssetElementFromPath = exports.getAssetElement = void 0;
const media_utils_1 = require("@remotion/media-utils");
const studio_shared_1 = require("@remotion/studio-shared");
const remotion_1 = require("remotion");
const no_react_1 = require("remotion/no-react");
const get_static_files_1 = require("../api/get-static-files");
const write_static_file_1 = require("../api/write-static-file");
const install_required_package_1 = require("../helpers/install-required-package");
const call_api_1 = require("./call-api");
const NotificationCenter_1 = require("./Notifications/NotificationCenter");
const getAssetElement = ({ fileType, src, }) => {
    if (fileType.type === 'webp' && fileType.animated) {
        return {
            type: 'asset',
            assetType: 'animated-image',
            src,
            srcType: 'static',
            dimensions: fileType.dimensions,
            position: null,
        };
    }
    if (fileType.type === 'png' ||
        fileType.type === 'jpeg' ||
        fileType.type === 'webp' ||
        fileType.type === 'bmp') {
        return {
            type: 'asset',
            assetType: 'image',
            src,
            srcType: 'static',
            dimensions: fileType.dimensions,
            position: null,
        };
    }
    if (fileType.type === 'apng') {
        return {
            type: 'asset',
            assetType: 'animated-image',
            src,
            srcType: 'static',
            dimensions: fileType.dimensions,
            position: null,
        };
    }
    if (fileType.type === 'gif') {
        return {
            type: 'asset',
            assetType: 'gif',
            src,
            srcType: 'static',
            dimensions: fileType.dimensions,
            position: null,
        };
    }
    if (fileType.type === 'riff' ||
        fileType.type === 'webm' ||
        fileType.type === 'iso-base-media' ||
        fileType.type === 'transport-stream') {
        return {
            type: 'asset',
            assetType: 'video',
            src,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    if (fileType.type === 'wav' ||
        fileType.type === 'mp3' ||
        fileType.type === 'aac' ||
        fileType.type === 'flac') {
        return {
            type: 'asset',
            assetType: 'audio',
            src,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    return null;
};
exports.getAssetElement = getAssetElement;
const getAssetElementFromPath = (assetPath) => {
    var _a;
    if (!assetPath || assetPath.includes('\\')) {
        return null;
    }
    const extension = (_a = assetPath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
    if (!extension || extension === assetPath.toLowerCase()) {
        return null;
    }
    if (['png', 'jpg', 'jpeg', 'webp', 'bmp', 'svg'].includes(extension)) {
        return {
            type: 'asset',
            assetType: 'image',
            src: assetPath,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    if (extension === 'apng') {
        return {
            type: 'asset',
            assetType: 'animated-image',
            src: assetPath,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    if (extension === 'gif') {
        return {
            type: 'asset',
            assetType: 'gif',
            src: assetPath,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    if (['mp4', 'm4v', 'mov', 'avi', 'webm', 'ts', 'm2ts'].includes(extension)) {
        return {
            type: 'asset',
            assetType: 'video',
            src: assetPath,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    if (['wav', 'mp3', 'aac', 'flac'].includes(extension)) {
        return {
            type: 'asset',
            assetType: 'audio',
            src: assetPath,
            srcType: 'static',
            dimensions: null,
            position: null,
        };
    }
    return null;
};
exports.getAssetElementFromPath = getAssetElementFromPath;
const getAssetElementForDroppedFile = ({ fileType, src, }) => {
    const detectedElement = (0, exports.getAssetElement)({ fileType, src });
    if (detectedElement !== null) {
        return detectedElement;
    }
    if (fileType.type !== 'unknown' || !src.toLowerCase().endsWith('.svg')) {
        return null;
    }
    return (0, exports.getAssetElementFromPath)(src);
};
exports.getAssetElementForDroppedFile = getAssetElementForDroppedFile;
const getAssetLabel = (element) => {
    if (element.type !== 'asset') {
        throw new Error('Expected asset element');
    }
    if (element.assetType === 'image') {
        return '<Img>';
    }
    if (element.assetType === 'video') {
        return '<Video>';
    }
    if (element.assetType === 'gif') {
        return '<Gif>';
    }
    if (element.assetType === 'animated-image') {
        return '<AnimatedImage>';
    }
    if (element.assetType === 'audio') {
        return '<Audio>';
    }
    throw new Error('Unsupported asset type');
};
const getComponentLabel = (component) => {
    return `<${component.componentName}>`;
};
const getCenteredPosition = ({ dimensions, dropPosition, }) => {
    if (dropPosition === null) {
        return null;
    }
    if (dimensions === null) {
        return {
            x: dropPosition.centerX,
            y: dropPosition.centerY,
        };
    }
    return {
        x: dropPosition.centerX - dimensions.width / 2,
        y: dropPosition.centerY - dimensions.height / 2,
    };
};
const getElementPositionForDrop = ({ dimensions, dropPosition, }) => {
    if (dimensions === null) {
        return null;
    }
    return getCenteredPosition({ dimensions, dropPosition });
};
exports.getElementPositionForDrop = getElementPositionForDrop;
const getCompositionPositionForDrop = ({ compositionDimensions, destinationDimensions, dropPosition, }) => {
    // No translation makes an equal-sized composition fill the destination.
    if (destinationDimensions !== null &&
        compositionDimensions.width === destinationDimensions.width &&
        compositionDimensions.height === destinationDimensions.height) {
        return null;
    }
    return getCenteredPosition({
        dimensions: compositionDimensions,
        dropPosition,
    });
};
exports.getCompositionPositionForDrop = getCompositionPositionForDrop;
const getAssetPositionForDrop = ({ assetDimensions, destinationDimensions, dropPosition, }) => {
    if (assetDimensions !== null &&
        destinationDimensions !== null &&
        assetDimensions.width === destinationDimensions.width &&
        assetDimensions.height === destinationDimensions.height) {
        return null;
    }
    return getCenteredPosition({ dimensions: assetDimensions, dropPosition });
};
exports.getAssetPositionForDrop = getAssetPositionForDrop;
const getComponentPropNumber = (props, name) => {
    const prop = props.find((p) => p.name === name);
    return typeof (prop === null || prop === void 0 ? void 0 : prop.value) === 'number' ? prop.value : null;
};
const getComponentDimensions = (component) => {
    if (component.dimensions) {
        return component.dimensions;
    }
    const width = getComponentPropNumber(component.props, 'width');
    const height = getComponentPropNumber(component.props, 'height');
    if (width !== null && height !== null) {
        return { width, height };
    }
    const radius = getComponentPropNumber(component.props, 'radius');
    if (radius !== null) {
        return { width: radius * 2, height: radius * 2 };
    }
    return null;
};
exports.getComponentDimensions = getComponentDimensions;
const getImageDimensions = ({ revokeObjectUrl, src, }) => {
    return new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = () => {
            if (revokeObjectUrl) {
                URL.revokeObjectURL(src);
            }
            resolve({ width: image.naturalWidth, height: image.naturalHeight });
        };
        image.onerror = () => {
            if (revokeObjectUrl) {
                URL.revokeObjectURL(src);
            }
            reject(new Error('Failed to load image dimensions'));
        };
        image.src = src;
    });
};
const getVideoDimensions = async (src) => {
    const metadata = await (0, media_utils_1.getVideoMetadata)(src);
    return { width: metadata.width, height: metadata.height };
};
const getFileDimensions = async ({ file, fileType, }) => {
    if (fileType.type === 'unknown' && file.name.toLowerCase().endsWith('.svg')) {
        const objectUrl = URL.createObjectURL(file);
        return getImageDimensions({ revokeObjectUrl: true, src: objectUrl });
    }
    if (fileType.type === 'wav' ||
        fileType.type === 'mp3' ||
        fileType.type === 'aac' ||
        fileType.type === 'flac') {
        return null;
    }
    if (fileType.type === 'png' ||
        fileType.type === 'jpeg' ||
        fileType.type === 'webp' ||
        fileType.type === 'bmp' ||
        fileType.type === 'apng' ||
        fileType.type === 'gif') {
        if (fileType.dimensions) {
            return fileType.dimensions;
        }
        const objectUrl = URL.createObjectURL(file);
        return getImageDimensions({ revokeObjectUrl: true, src: objectUrl });
    }
    if (fileType.type === 'riff' ||
        fileType.type === 'webm' ||
        fileType.type === 'iso-base-media' ||
        fileType.type === 'transport-stream') {
        const objectUrl = URL.createObjectURL(file);
        try {
            return await getVideoDimensions(objectUrl);
        }
        finally {
            URL.revokeObjectURL(objectUrl);
        }
    }
    return null;
};
const getStaticAssetDimensions = (assetPath) => {
    var _a;
    const extension = (_a = assetPath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
    const src = (0, remotion_1.staticFile)(assetPath);
    if (extension &&
        ['png', 'jpg', 'jpeg', 'webp', 'bmp', 'gif', 'apng', 'svg'].includes(extension)) {
        return getImageDimensions({ revokeObjectUrl: false, src });
    }
    if (extension &&
        ['mp4', 'm4v', 'mov', 'avi', 'webm', 'ts', 'm2ts'].includes(extension)) {
        return getVideoDimensions(src);
    }
    return null;
};
const getFileDimensionsOrNull = async ({ file, fileType, }) => {
    try {
        return await getFileDimensions({ file, fileType });
    }
    catch (_a) {
        return null;
    }
};
const getStaticAssetDimensionsOrNull = async (assetPath) => {
    try {
        return await getStaticAssetDimensions(assetPath);
    }
    catch (_a) {
        return null;
    }
};
const getStaticAssetFileType = async (assetPath) => {
    var _a;
    const extension = (_a = assetPath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
    if (extension !== 'png' && extension !== 'apng' && extension !== 'webp') {
        return null;
    }
    try {
        const response = await fetch((0, remotion_1.staticFile)(assetPath));
        if (!response.ok) {
            return null;
        }
        return (0, studio_shared_1.detectFileType)(new Uint8Array(await response.arrayBuffer()));
    }
    catch (_b) {
        return null;
    }
};
const getAssetElementFromStaticAsset = async (assetPath) => {
    const fileType = await getStaticAssetFileType(assetPath);
    if (fileType) {
        const element = (0, exports.getAssetElement)({ fileType, src: assetPath });
        if (element) {
            return element;
        }
    }
    return (0, exports.getAssetElementFromPath)(assetPath);
};
const pickFilesToImport = ({ multiple = true, } = {}) => {
    return new Promise((resolve) => {
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = multiple;
        input.style.display = 'none';
        let didResolve = false;
        const resolveOnce = (files) => {
            if (didResolve) {
                return;
            }
            didResolve = true;
            input.removeEventListener('change', onChange);
            input.removeEventListener('cancel', onCancel);
            input.remove();
            resolve(files);
        };
        const onChange = () => {
            var _a;
            return resolveOnce(Array.from((_a = input.files) !== null && _a !== void 0 ? _a : []));
        };
        const onCancel = () => resolveOnce([]);
        input.addEventListener('change', onChange);
        input.addEventListener('cancel', onCancel);
        document.body.appendChild(input);
        input.click();
    });
};
exports.pickFilesToImport = pickFilesToImport;
const notifyInsertedAssets = (insertedLabels) => {
    if (insertedLabels.length === 1) {
        (0, NotificationCenter_1.showNotification)(`Added ${insertedLabels[0]} to source file`, 2000);
    }
    else if (insertedLabels.length > 1) {
        (0, NotificationCenter_1.showNotification)(`Added ${insertedLabels.length} assets to source file`, 2000);
    }
};
const notifyUnsupportedFiles = (unsupportedFiles) => {
    if (unsupportedFiles.length === 1) {
        (0, NotificationCenter_1.showNotification)(`Cannot add ${unsupportedFiles[0]}: Unsupported file type`, 3000);
    }
    else if (unsupportedFiles.length > 1) {
        (0, NotificationCenter_1.showNotification)(`Skipped ${unsupportedFiles.length} unsupported files`, 3000);
    }
};
const insertAssetElement = async ({ compositionFile, compositionId, element, }) => {
    const requiredPackage = (0, studio_shared_1.getRequiredPackageForInsertableElement)(element);
    await (0, install_required_package_1.installRequiredPackages)(requiredPackage ? [requiredPackage] : []);
    const result = await (0, call_api_1.callApi)('/api/insert-jsx-element', {
        compositionFile,
        compositionId,
        element,
    });
    if (!result.success) {
        (0, NotificationCenter_1.showNotification)(result.reason, 4000);
        return false;
    }
    return true;
};
const downloadRemoteAsset = (url) => {
    return (0, call_api_1.callApi)('/api/download-remote-asset', { url });
};
const importAssets = async ({ compositionFile, compositionId, destinationDimensions, dropPosition, files, }) => {
    var _a;
    if (files.length === 0) {
        return;
    }
    const staticFiles = (0, get_static_files_1.getStaticFiles)();
    const differentExistingFile = files.find((file) => {
        return staticFiles.some((existingStaticFile) => existingStaticFile.name === file.name &&
            existingStaticFile.sizeInBytes !== file.size);
    });
    if (differentExistingFile) {
        (0, NotificationCenter_1.showNotification)(`File with name ${differentExistingFile.name} already exists and is different`, 4000);
        return;
    }
    const insertedLabels = [];
    const addedStaticFiles = [];
    const unsupportedFiles = [];
    const notifyAddedStaticFiles = () => {
        if (addedStaticFiles.length === 1) {
            (0, NotificationCenter_1.showNotification)(`Created ${addedStaticFiles[0]} in public folder`, 3000);
        }
        else if (addedStaticFiles.length > 1) {
            (0, NotificationCenter_1.showNotification)(`Added ${addedStaticFiles.length} files to public folder`, 3000);
        }
        addedStaticFiles.length = 0;
    };
    try {
        for (const file of files) {
            const contents = await file.arrayBuffer();
            const fileType = (0, studio_shared_1.detectFileType)(new Uint8Array(contents));
            const element = (0, exports.getAssetElementForDroppedFile)({
                fileType,
                src: file.name,
            });
            if (element === null) {
                unsupportedFiles.push(file.name);
                continue;
            }
            const alreadyExists = staticFiles.some((existingStaticFile) => existingStaticFile.name === file.name &&
                existingStaticFile.sizeInBytes === file.size);
            if (!alreadyExists) {
                await (0, write_static_file_1.writeStaticFile)({
                    contents,
                    filePath: file.name,
                });
                addedStaticFiles.push(file.name);
            }
            const dimensions = await getFileDimensionsOrNull({ file, fileType });
            const resolvedDimensions = (_a = element.dimensions) !== null && _a !== void 0 ? _a : dimensions;
            const inserted = await insertAssetElement({
                compositionFile,
                compositionId,
                element: {
                    ...element,
                    dimensions: resolvedDimensions,
                    position: (0, exports.getAssetPositionForDrop)({
                        assetDimensions: resolvedDimensions,
                        destinationDimensions,
                        dropPosition,
                    }),
                },
            });
            if (!inserted) {
                notifyAddedStaticFiles();
                return;
            }
            insertedLabels.push(getAssetLabel(element));
        }
        notifyAddedStaticFiles();
        notifyInsertedAssets(insertedLabels);
        notifyUnsupportedFiles(unsupportedFiles);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add asset: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.importAssets = importAssets;
const importRemoteAsset = async ({ compositionFile, compositionId, destinationDimensions, dropPosition, url, }) => {
    try {
        const { assetPath, created, element } = await downloadRemoteAsset(url);
        if (created) {
            (0, NotificationCenter_1.showNotification)(`Created ${assetPath} in public folder`, 3000);
        }
        if (element.type !== 'asset') {
            (0, NotificationCenter_1.showNotification)('Cannot add remote asset: Unsupported asset type', 3000);
            return;
        }
        const inserted = await insertAssetElement({
            compositionFile,
            compositionId,
            element: {
                ...element,
                position: (0, exports.getAssetPositionForDrop)({
                    assetDimensions: element.dimensions,
                    destinationDimensions,
                    dropPosition,
                }),
            },
        });
        if (!inserted) {
            return;
        }
        notifyInsertedAssets([getAssetLabel(element)]);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add remote asset: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.importRemoteAsset = importRemoteAsset;
const insertRemoteAudio = async ({ compositionFile, compositionId, url, }) => {
    if (!(0, studio_shared_1.isUrl)(url)) {
        (0, NotificationCenter_1.showNotification)('Cannot add sound effect: Unsupported URL', 3000);
        return;
    }
    const element = {
        type: 'asset',
        assetType: 'audio',
        src: url,
        srcType: 'remote',
        dimensions: null,
        position: null,
    };
    try {
        const inserted = await insertAssetElement({
            compositionFile,
            compositionId,
            element,
        });
        if (!inserted) {
            return;
        }
        notifyInsertedAssets([getAssetLabel(element)]);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add sound effect: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.insertRemoteAudio = insertRemoteAudio;
const insertExistingAssets = async ({ assetPaths, compositionFile, compositionId, destinationDimensions, dropPosition, }) => {
    var _a;
    if (assetPaths.length === 0) {
        return;
    }
    const insertedLabels = [];
    const unsupportedFiles = [];
    try {
        for (const assetPath of assetPaths) {
            const element = await getAssetElementFromStaticAsset(assetPath);
            if (element === null) {
                unsupportedFiles.push(assetPath);
                continue;
            }
            const dimensions = (_a = element.dimensions) !== null && _a !== void 0 ? _a : (await getStaticAssetDimensionsOrNull(assetPath));
            const inserted = await insertAssetElement({
                compositionFile,
                compositionId,
                element: {
                    ...element,
                    dimensions,
                    position: (0, exports.getAssetPositionForDrop)({
                        assetDimensions: dimensions,
                        destinationDimensions,
                        dropPosition,
                    }),
                },
            });
            if (!inserted) {
                return;
            }
            insertedLabels.push(getAssetLabel(element));
        }
        notifyInsertedAssets(insertedLabels);
        notifyUnsupportedFiles(unsupportedFiles);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add asset: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.insertExistingAssets = insertExistingAssets;
const insertComponent = async ({ component, compositionFile, compositionId, dropPosition, }) => {
    try {
        const inserted = await insertAssetElement({
            compositionFile,
            compositionId,
            element: {
                type: 'component',
                componentName: component.componentName,
                importName: component.importName,
                importPath: component.importPath,
                props: component.props,
                position: getCenteredPosition({
                    dimensions: (0, exports.getComponentDimensions)(component),
                    dropPosition,
                }),
            },
        });
        if (!inserted) {
            return;
        }
        (0, NotificationCenter_1.showNotification)(`Added ${getComponentLabel(component)} to source file`, 2000);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add component: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.insertComponent = insertComponent;
const serializeResolvedPropsForSourceCode = (props) => {
    return no_react_1.NoReactInternals.serializeJSONWithSpecialTypes({
        data: props,
        indent: undefined,
        staticBase: window.remotion_staticBase,
    }).serializedString;
};
const insertComposition = async ({ composition, compositionFile, compositionId, destinationDimensions, dropPosition, }) => {
    if (composition.compositionId === compositionId) {
        (0, NotificationCenter_1.showNotification)('Cannot add a composition to itself', 3000);
        return;
    }
    if (composition.compositionFile === null) {
        (0, NotificationCenter_1.showNotification)('Could not find composition source file', 3000);
        return;
    }
    try {
        const resolver = remotion_1.Internals.resolveCompositionsRef.current;
        if (!resolver) {
            throw new Error('No composition resolver available');
        }
        const calculated = await resolver.resolveComposition(composition.compositionId);
        const dimensions = {
            width: calculated.width,
            height: calculated.height,
        };
        const inserted = await insertAssetElement({
            compositionFile,
            compositionId,
            element: {
                type: 'composition',
                compositionId: composition.compositionId,
                compositionFile: composition.compositionFile,
                durationInFrames: calculated.durationInFrames,
                width: calculated.width,
                height: calculated.height,
                serializedResolvedPropsWithCustomSchema: serializeResolvedPropsForSourceCode(calculated.props),
                position: (0, exports.getCompositionPositionForDrop)({
                    compositionDimensions: dimensions,
                    destinationDimensions,
                    dropPosition,
                }),
            },
        });
        if (!inserted) {
            return;
        }
        (0, NotificationCenter_1.showNotification)(`Added ${composition.compositionId} to ${compositionId}`, 2000);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add composition: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.insertComposition = insertComposition;
const insertElement = async ({ compositionFile, compositionId, dropPosition, element, }) => {
    try {
        await (0, install_required_package_1.installRequiredPackages)(element.dependencies);
        const response = await (0, call_api_1.callApi)('/api/insert-element', {
            compositionFile,
            compositionId,
            element,
            position: (0, exports.getElementPositionForDrop)({
                dimensions: element.dimensions,
                dropPosition,
            }),
        });
        if (!response.success) {
            (0, NotificationCenter_1.showNotification)(`Could not add Element: ${response.reason}`, 4000);
            return;
        }
        (0, NotificationCenter_1.showNotification)(`Added ${element.displayName} to source file`, 2000);
    }
    catch (error) {
        (0, NotificationCenter_1.showNotification)(`Could not add Element: ${error instanceof Error ? error.message : String(error)}`, 4000);
    }
};
exports.insertElement = insertElement;
