/* -----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { defaultSanitizer } from '@jupyterlab/apputils';
import { PathExt, URLExt } from '@jupyterlab/coreutils';
import { nullTranslator } from '@jupyterlab/translation';
import { MimeModel } from './mimemodel';
/**
 * An object which manages mime renderer factories.
 *
 * This object is used to render mime models using registered mime
 * renderers, selecting the preferred mime renderer to render the
 * model into a widget.
 *
 * #### Notes
 * This class is not intended to be subclassed.
 */
export class RenderMimeRegistry {
    /**
     * Construct a new rendermime.
     *
     * @param options - The options for initializing the instance.
     */
    constructor(options = {}) {
        this._id = 0;
        this._ranks = {};
        this._types = null;
        this._factories = {};
        // Parse the options.
        this.translator = options.translator || nullTranslator;
        this.resolver = options.resolver || null;
        this.linkHandler = options.linkHandler || null;
        this.latexTypesetter = options.latexTypesetter || null;
        this.sanitizer = options.sanitizer || defaultSanitizer;
        // Add the initial factories.
        if (options.initialFactories) {
            for (const factory of options.initialFactories) {
                this.addFactory(factory);
            }
        }
    }
    /**
     * The ordered list of mimeTypes.
     */
    get mimeTypes() {
        return this._types || (this._types = Private.sortedTypes(this._ranks));
    }
    /**
     * Find the preferred mime type for a mime bundle.
     *
     * @param bundle - The bundle of mime data.
     *
     * @param safe - How to consider safe/unsafe factories. If 'ensure',
     *   it will only consider safe factories. If 'any', any factory will be
     *   considered. If 'prefer', unsafe factories will be considered, but
     *   only after the safe options have been exhausted.
     *
     * @returns The preferred mime type from the available factories,
     *   or `undefined` if the mime type cannot be rendered.
     */
    preferredMimeType(bundle, safe = 'ensure') {
        // Try to find a safe factory first, if preferred.
        if (safe === 'ensure' || safe === 'prefer') {
            for (const mt of this.mimeTypes) {
                if (mt in bundle && this._factories[mt].safe) {
                    return mt;
                }
            }
        }
        if (safe !== 'ensure') {
            // Otherwise, search for the best factory among all factories.
            for (const mt of this.mimeTypes) {
                if (mt in bundle) {
                    return mt;
                }
            }
        }
        // Otherwise, no matching mime type exists.
        return undefined;
    }
    /**
     * Create a renderer for a mime type.
     *
     * @param mimeType - The mime type of interest.
     *
     * @returns A new renderer for the given mime type.
     *
     * @throws An error if no factory exists for the mime type.
     */
    createRenderer(mimeType) {
        // Throw an error if no factory exists for the mime type.
        if (!(mimeType in this._factories)) {
            throw new Error(`No factory for mime type: '${mimeType}'`);
        }
        // Invoke the best factory for the given mime type.
        return this._factories[mimeType].createRenderer({
            mimeType,
            resolver: this.resolver,
            sanitizer: this.sanitizer,
            linkHandler: this.linkHandler,
            latexTypesetter: this.latexTypesetter,
            translator: this.translator
        });
    }
    /**
     * Create a new mime model.  This is a convenience method.
     *
     * @options - The options used to create the model.
     *
     * @returns A new mime model.
     */
    createModel(options = {}) {
        return new MimeModel(options);
    }
    /**
     * Create a clone of this rendermime instance.
     *
     * @param options - The options for configuring the clone.
     *
     * @returns A new independent clone of the rendermime.
     */
    clone(options = {}) {
        // Create the clone.
        const clone = new RenderMimeRegistry({
            resolver: options.resolver || this.resolver || undefined,
            sanitizer: options.sanitizer || this.sanitizer || undefined,
            linkHandler: options.linkHandler || this.linkHandler || undefined,
            latexTypesetter: options.latexTypesetter || this.latexTypesetter || undefined,
            translator: this.translator
        });
        // Clone the internal state.
        clone._factories = Object.assign({}, this._factories);
        clone._ranks = Object.assign({}, this._ranks);
        clone._id = this._id;
        // Return the cloned object.
        return clone;
    }
    /**
     * Get the renderer factory registered for a mime type.
     *
     * @param mimeType - The mime type of interest.
     *
     * @returns The factory for the mime type, or `undefined`.
     */
    getFactory(mimeType) {
        return this._factories[mimeType];
    }
    /**
     * Add a renderer factory to the rendermime.
     *
     * @param factory - The renderer factory of interest.
     *
     * @param rank - The rank of the renderer. A lower rank indicates
     *   a higher priority for rendering. If not given, the rank will
     *   defer to the `defaultRank` of the factory.  If no `defaultRank`
     *   is given, it will default to 100.
     *
     * #### Notes
     * The renderer will replace an existing renderer for the given
     * mimeType.
     */
    addFactory(factory, rank) {
        if (rank === undefined) {
            rank = factory.defaultRank;
            if (rank === undefined) {
                rank = 100;
            }
        }
        for (const mt of factory.mimeTypes) {
            this._factories[mt] = factory;
            this._ranks[mt] = { rank, id: this._id++ };
        }
        this._types = null;
    }
    /**
     * Remove a mime type.
     *
     * @param mimeType - The mime type of interest.
     */
    removeMimeType(mimeType) {
        delete this._factories[mimeType];
        delete this._ranks[mimeType];
        this._types = null;
    }
    /**
     * Get the rank for a given mime type.
     *
     * @param mimeType - The mime type of interest.
     *
     * @returns The rank of the mime type or undefined.
     */
    getRank(mimeType) {
        const rank = this._ranks[mimeType];
        return rank && rank.rank;
    }
    /**
     * Set the rank of a given mime type.
     *
     * @param mimeType - The mime type of interest.
     *
     * @param rank - The new rank to assign.
     *
     * #### Notes
     * This is a no-op if the mime type is not registered.
     */
    setRank(mimeType, rank) {
        if (!this._ranks[mimeType]) {
            return;
        }
        const id = this._id++;
        this._ranks[mimeType] = { rank, id };
        this._types = null;
    }
}
/**
 * The namespace for `RenderMimeRegistry` class statics.
 */
(function (RenderMimeRegistry) {
    /**
     * A default resolver that uses a given reference path and a contents manager.
     */
    class UrlResolver {
        /**
         * Create a new url resolver.
         */
        constructor(options) {
            if (options.path) {
                this._path = options.path;
            }
            else if (options.session) {
                this._session = options.session;
            }
            else {
                throw new Error("Either 'path' or 'session' must be given as a constructor option");
            }
            this._contents = options.contents;
        }
        /**
         * The path of the object, from which local urls can be derived.
         */
        get path() {
            var _a;
            return (_a = this._path) !== null && _a !== void 0 ? _a : this._session.path;
        }
        set path(value) {
            this._path = value;
        }
        /**
         * Resolve a relative url to an absolute url path.
         */
        async resolveUrl(url) {
            if (this.isLocal(url)) {
                const cwd = encodeURI(PathExt.dirname(this.path));
                url = PathExt.resolve(cwd, url);
            }
            return url;
        }
        /**
         * Get the download url of a given absolute url path.
         *
         * #### Notes
         * The returned URL may include a query parameter.
         */
        async getDownloadUrl(urlPath) {
            if (this.isLocal(urlPath)) {
                // decode url->path before passing to contents api
                return this._contents.getDownloadUrl(decodeURIComponent(urlPath));
            }
            return urlPath;
        }
        /**
         * Whether the URL should be handled by the resolver
         * or not.
         *
         * #### Notes
         * This is similar to the `isLocal` check in `URLExt`,
         * but it also checks whether the path points to any
         * of the `IDrive`s that may be registered with the contents
         * manager.
         */
        isLocal(url) {
            if (this.isMalformed(url)) {
                return false;
            }
            return URLExt.isLocal(url) || !!this._contents.driveName(decodeURI(url));
        }
        /**
         * Whether the URL can be decoded using `decodeURI`.
         */
        isMalformed(url) {
            try {
                decodeURI(url);
                return false;
            }
            catch (error) {
                if (error instanceof URIError) {
                    return true;
                }
                throw error;
            }
        }
    }
    RenderMimeRegistry.UrlResolver = UrlResolver;
})(RenderMimeRegistry || (RenderMimeRegistry = {}));
/**
 * The namespace for the module implementation details.
 */
var Private;
(function (Private) {
    /**
     * Get the mime types in the map, ordered by rank.
     */
    function sortedTypes(map) {
        return Object.keys(map).sort((a, b) => {
            const p1 = map[a];
            const p2 = map[b];
            if (p1.rank !== p2.rank) {
                return p1.rank - p2.rank;
            }
            return p1.id - p2.id;
        });
    }
    Private.sortedTypes = sortedTypes;
    function sessionConnection(s) {
        return s.sessionChanged
            ? s.session
            : s;
    }
    Private.sessionConnection = sessionConnection;
})(Private || (Private = {}));
//# sourceMappingURL=registry.js.map