import { Decoration, ViewPlugin, WidgetType, } from '@codemirror/view';
import { getProportionalSize } from "../../../utils/index.js";
import { FileUploadHandlerFacet } from "../files-upload-facet.js";
import { LoggerFacet } from "../logger-facet.js";
import { ReactRendererFacet } from "../react-facet.js";
import { IMG_MAX_HEIGHT, SUCCESS_UPLOAD_REMOVE_TIMEOUT } from "./const.js";
import { AddUploadWidgetEffect, RemoveUploadWidgetEffect } from "./effects.js";
import { getImageDimensions, getTransferFiles, isImageFile, uniqueId } from "./utils.js";
import { renderWidget } from "./widget.js";
class FileUploadWidget extends WidgetType {
    selfId;
    presenter;
    renderer = null;
    props = {};
    constructor(id) {
        super();
        this.selfId = id;
    }
    toDOM(view) {
        const dom = document.createElement('div');
        dom.className = 'cm-file-upload-widget';
        dom.style.display = 'inline-block';
        this.renderer?.remove();
        this.createRenderItem(dom, view);
        return dom;
    }
    updateDOM(dom, view) {
        this.renderer?.remove();
        this.createRenderItem(dom, view);
        return true;
    }
    destroy(dom) {
        super.destroy(dom);
        this.renderer?.remove();
        this.presenter?.cancel();
        this.presenter = undefined;
    }
    setPresenter(presenter) {
        this.presenter = presenter;
    }
    render(props) {
        Object.assign(this.props, props);
        this.renderer?.rerender();
    }
    createRenderItem(dom, view) {
        this.renderer = view.state
            .facet(ReactRendererFacet)
            .createItem('cm-file-upload-widget', () => renderWidget(dom, { ...this.props }));
    }
}
class FileUploadPresenter {
    widget;
    file;
    view;
    uploader;
    needDimensionsForImages;
    enableNewImageSizeCalculation;
    state = 'initial';
    constructor(params) {
        this.file = params.file;
        this.view = params.view;
        this.widget = params.widget;
        this.uploader = params.uploader;
        this.needDimensionsForImages = params.needDimensionsForImages;
        this.enableNewImageSizeCalculation = params.enableNewImageSizeCalculation;
        this.widget.setPresenter(this);
        this.run();
    }
    cancel() {
        this.state = 'canceled';
    }
    run() {
        this.state = 'uploading';
        this.widget.render({
            status: 'uploading',
            fileName: this.file.name,
            fileType: isImageFile(this.file) ? 'image' : 'file',
        });
        this.uploader(this.file).then(this.onUploadSuccess, this.onUploadError);
    }
    onUploadSuccess = (res) => {
        if (this.state === 'canceled')
            return;
        this.state = 'success';
        this.widget.render({
            status: 'success',
            fileName: this.file.name,
            fileType: isImageFile(this.file) ? 'image' : 'file',
        });
        setTimeout(async () => {
            const markup = await this.formatFileMarkup(res);
            if (this.state === 'canceled')
                return;
            this.view.dispatch({
                effects: RemoveUploadWidgetEffect.of({
                    id: this.widget.selfId,
                    markup,
                }),
            });
        }, SUCCESS_UPLOAD_REMOVE_TIMEOUT);
    };
    onUploadError = (err) => {
        if (this.state === 'canceled')
            return;
        this.state = 'error';
        this.widget.render({
            status: 'error',
            fileName: this.file.name,
            fileType: isImageFile(this.file) ? 'image' : 'file',
            errorText: String(err),
            onReUploadClick: () => {
                if (this.state === 'error')
                    this.run();
            },
        });
    };
    async formatFileMarkup(res) {
        const fileName = res.name ?? this.file.name ?? '';
        let markup = `![${fileName}](${res.url}`;
        if (isImageFile(this.file)) {
            if (this.needDimensionsForImages) {
                try {
                    const fileSize = await getImageDimensions(this.file);
                    if (this.enableNewImageSizeCalculation) {
                        const { width, height } = getProportionalSize({
                            width: fileSize.width,
                            height: fileSize.height,
                            imgMaxHeight: IMG_MAX_HEIGHT,
                        });
                        markup += ` =${width}x${height}`;
                    }
                    else {
                        const height = Math.min(fileSize.height, IMG_MAX_HEIGHT);
                        markup += ` =x${height}`;
                    }
                }
                catch (err) {
                    console.error(err);
                }
            }
            markup += `)`;
        }
        else {
            markup = `{% file src="${res.url}" name="${fileName.replace('"', '')}" %}`;
        }
        return markup;
    }
}
export const FilesUploadPlugin = ViewPlugin.fromClass(class {
    decos = Decoration.none;
    view;
    constructor(view) {
        this.view = view;
    }
    update(update) {
        this.decos = this.decos.map(update.changes);
        const uploadFacet = this.view.state.facet(FileUploadHandlerFacet);
        const changes = [];
        for (const tr of update.transactions) {
            for (const eff of tr.effects) {
                if (eff.is(AddUploadWidgetEffect)) {
                    this.decos = this.decos.update({
                        add: Array.from(eff.value.files).map((file, index) => {
                            const { widget } = new FileUploadPresenter({
                                file,
                                view: this.view,
                                uploader: uploadFacet.fn,
                                needDimensionsForImages: Boolean(uploadFacet.imageWithDimensions),
                                enableNewImageSizeCalculation: Boolean(uploadFacet.enableNewImageSizeCalculation),
                                widget: new FileUploadWidget(uniqueId('__file_widget_id')),
                            });
                            return Decoration.widget({
                                widget,
                                side: 1,
                                __file_widget_id: widget.selfId,
                            }).range(eff.value.pos + index);
                        }),
                    });
                }
                if (eff.is(RemoveUploadWidgetEffect)) {
                    this.decos = this.decos.update({
                        filter: (from, to, deco) => {
                            if (deco.spec.__file_widget_id !== eff.value.id)
                                return true;
                            if (eff.value.markup) {
                                changes.push({ from, to, insert: eff.value.markup });
                            }
                            return false;
                        },
                    });
                }
            }
        }
        if (changes.length) {
            setTimeout(() => {
                this.view.dispatch({ changes });
            });
        }
    }
}, {
    decorations: (v) => v.decos,
    eventHandlers: {
        paste(event, view) {
            if (!event.clipboardData)
                return false;
            const files = getTransferFiles(event.clipboardData);
            if (!files)
                return false;
            const { from, to } = view.state.selection.main;
            view.state.facet(LoggerFacet).event({
                domEvent: 'paste',
                event: 'paste-files',
                plugin: 'files-upload',
                dataTypes: event.clipboardData.types,
            });
            view.dispatch({
                selection: { anchor: from },
                effects: AddUploadWidgetEffect.of({ files, pos: from }),
                changes: { from, to, insert: ' '.repeat(files.length) },
            });
            event.preventDefault();
            return true;
        },
        drop(event, view) {
            if (!event.dataTransfer)
                return false;
            const files = getTransferFiles(event.dataTransfer);
            if (!files)
                return false;
            view.state.facet(LoggerFacet).event({
                domEvent: 'drop',
                event: 'drop-files',
                plugin: 'files-upload',
                dataTypes: event.dataTransfer.types,
            });
            const pos = this.view.posAtCoords(event, false);
            view.dispatch({
                selection: { anchor: pos },
                effects: AddUploadWidgetEffect.of({ files, pos }),
                changes: { from: pos, insert: ' '.repeat(files.length) },
            });
            event.preventDefault();
            return true;
        },
    },
});
//# sourceMappingURL=plugin.js.map
