// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { Printing, showErrorMessage } from '@jupyterlab/apputils';
import { ActivityMonitor } from '@jupyterlab/coreutils';
import { MimeModel } from '@jupyterlab/rendermime';
import { nullTranslator } from '@jupyterlab/translation';
import { JSONExt, PromiseDelegate } from '@lumino/coreutils';
import { MessageLoop } from '@lumino/messaging';
import { StackedLayout, Widget } from '@lumino/widgets';
import { ABCWidgetFactory, DocumentWidget } from './default';
/**
 * A content widget for a rendered mimetype document.
 */
export class MimeContent extends Widget {
    /**
     * Construct a new widget.
     */
    constructor(options) {
        super();
        /**
         * A bound change callback.
         */
        this._changeCallback = (options) => {
            if (!options.data || !options.data[this.mimeType]) {
                return;
            }
            const data = options.data[this.mimeType];
            if (typeof data === 'string') {
                if (data !== this._context.model.toString()) {
                    this._context.model.fromString(data);
                }
            }
            else if (data !== null &&
                data !== undefined &&
                !JSONExt.deepEqual(data, this._context.model.toJSON())) {
                this._context.model.fromJSON(data);
            }
        };
        this._fragment = '';
        this._ready = new PromiseDelegate();
        this._isRendering = false;
        this._renderRequested = false;
        this.addClass('jp-MimeDocument');
        this.translator = options.translator || nullTranslator;
        this._trans = this.translator.load('jupyterlab');
        this.mimeType = options.mimeType;
        this._dataType = options.dataType || 'string';
        this._context = options.context;
        this.renderer = options.renderer;
        const layout = (this.layout = new StackedLayout());
        layout.addWidget(this.renderer);
        this._context.ready
            .then(() => {
            return this._render();
        })
            .then(() => {
            // After rendering for the first time, send an activation request if we
            // are currently focused.
            if (this.node === document.activeElement) {
                // We want to synchronously send (not post) the activate message, while
                // we know this node still has focus.
                MessageLoop.sendMessage(this.renderer, Widget.Msg.ActivateRequest);
            }
            // Throttle the rendering rate of the widget.
            this._monitor = new ActivityMonitor({
                signal: this._context.model.contentChanged,
                timeout: options.renderTimeout
            });
            this._monitor.activityStopped.connect(this.update, this);
            this._ready.resolve(undefined);
        })
            .catch(reason => {
            // Dispose the document if rendering fails.
            requestAnimationFrame(() => {
                this.dispose();
            });
            void showErrorMessage(this._trans.__('Renderer Failure: %1', this._context.path), reason);
        });
    }
    /**
     * Print method. Deferred to the renderer.
     */
    [Printing.symbol]() {
        return Printing.getPrintFunction(this.renderer);
    }
    /**
     * A promise that resolves when the widget is ready.
     */
    get ready() {
        return this._ready.promise;
    }
    /**
     * Set URI fragment identifier.
     */
    setFragment(fragment) {
        this._fragment = fragment;
        this.update();
    }
    /**
     * Dispose of the resources held by the widget.
     */
    dispose() {
        if (this.isDisposed) {
            return;
        }
        if (this._monitor) {
            this._monitor.dispose();
        }
        this._monitor = null;
        super.dispose();
    }
    /**
     * Handle an `update-request` message to the widget.
     */
    onUpdateRequest(msg) {
        if (this._context.isReady) {
            void this._render();
            this._fragment = '';
        }
    }
    /**
     * Render the mime content.
     */
    async _render() {
        if (this.isDisposed) {
            return;
        }
        // Since rendering is async, we note render requests that happen while we
        // actually are rendering for a future rendering.
        if (this._isRendering) {
            this._renderRequested = true;
            return;
        }
        // Set up for this rendering pass.
        this._renderRequested = false;
        const context = this._context;
        const model = context.model;
        const data = {};
        if (this._dataType === 'string') {
            data[this.mimeType] = model.toString();
        }
        else {
            data[this.mimeType] = model.toJSON();
        }
        const mimeModel = new MimeModel({
            data,
            callback: this._changeCallback,
            metadata: { fragment: this._fragment }
        });
        try {
            // Do the rendering asynchronously.
            this._isRendering = true;
            await this.renderer.renderModel(mimeModel);
            this._isRendering = false;
            // If there is an outstanding request to render, go ahead and render
            if (this._renderRequested) {
                return this._render();
            }
        }
        catch (reason) {
            // Dispose the document if rendering fails.
            requestAnimationFrame(() => {
                this.dispose();
            });
            void showErrorMessage(this._trans.__('Renderer Failure: %1', context.path), reason);
        }
    }
}
/**
 * A document widget for mime content.
 */
export class MimeDocument extends DocumentWidget {
    setFragment(fragment) {
        this.content.setFragment(fragment);
    }
}
/**
 * An implementation of a widget factory for a rendered mimetype document.
 */
export class MimeDocumentFactory extends ABCWidgetFactory {
    /**
     * Construct a new mimetype widget factory.
     */
    constructor(options) {
        super(Private.createRegistryOptions(options));
        this._rendermime = options.rendermime;
        this._renderTimeout = options.renderTimeout || 1000;
        this._dataType = options.dataType || 'string';
        this._fileType = options.primaryFileType;
        this._factory = options.factory;
    }
    /**
     * Create a new widget given a context.
     */
    createNewWidget(context) {
        var _a, _b;
        const ft = this._fileType;
        const mimeType = (ft === null || ft === void 0 ? void 0 : ft.mimeTypes.length) ? ft.mimeTypes[0] : 'text/plain';
        const rendermime = this._rendermime.clone({
            resolver: context.urlResolver
        });
        let renderer;
        if (this._factory && this._factory.mimeTypes.includes(mimeType)) {
            renderer = this._factory.createRenderer({
                mimeType,
                resolver: rendermime.resolver,
                sanitizer: rendermime.sanitizer,
                linkHandler: rendermime.linkHandler,
                latexTypesetter: rendermime.latexTypesetter
            });
        }
        else {
            renderer = rendermime.createRenderer(mimeType);
        }
        const content = new MimeContent({
            context,
            renderer,
            mimeType,
            renderTimeout: this._renderTimeout,
            dataType: this._dataType
        });
        content.title.icon = ft === null || ft === void 0 ? void 0 : ft.icon;
        content.title.iconClass = (_a = ft === null || ft === void 0 ? void 0 : ft.iconClass) !== null && _a !== void 0 ? _a : '';
        content.title.iconLabel = (_b = ft === null || ft === void 0 ? void 0 : ft.iconLabel) !== null && _b !== void 0 ? _b : '';
        const widget = new MimeDocument({ content, context });
        return widget;
    }
}
/**
 * The namespace for the module implementation details.
 */
var Private;
(function (Private) {
    /**
     * Create the document registry options.
     */
    function createRegistryOptions(options) {
        return Object.assign(Object.assign({}, options), { readOnly: true });
    }
    Private.createRegistryOptions = createRegistryOptions;
})(Private || (Private = {}));
//# sourceMappingURL=mimedocument.js.map