# Source: registry/layouts.js

    import {RegistryBase} from './base';
    import {applyNamespaces, deepCopy, mutate_attrs, merge, query_attrs, renameField, findFields} from '../helpers/layouts';
    import * as layouts from '../layouts';

    /**
     * Helper for working with predefined layouts
     *
     * This is part of the public interface with LocusZoom and a major way that users interact to configure plots.
     *
     * Each layout object that is added or retrieved here is a deep copy and totally independent from any other object
     * @public
     * @extends module:registry/base:RegistryBase
     * @inheritDoc
     */
    class LayoutRegistry extends RegistryBase {
        // Implemented as a "registry of registries"- one lookup each for panels, plots, etc...
        get(type, name, overrides = {}) {
            if (!(type && name)) {
                throw new Error('Must specify both the type and name for the layout desired. See .list() for available options');
            }
            // This is a registry of registries. Fetching an item may apply additional custom behaviors, such as
            //  applying overrides or applying namespaces.
            let base = super.get(type).get(name);

            // Most keys are merged directly. Namespaces are handled a little differently, as they act like global overrides.
            //  (eg ask for plot layout, and modify multiple nested data layers where a particular namespace is referenced)
            const custom_namespaces = overrides.namespace;
            if (!base.namespace) {
                // Iff namespaces are a top level key, we'll allow them to be merged directly with the base layout
                // NOTE: The "merge namespace" behavior means that data layers can add new data easily, but this method
                //   can't be used to remove namespaces when extending something. (you'll need to layout.namespaces = {} separately).
                delete overrides.namespace;
            }
            let result = merge(overrides, base);

            if (custom_namespaces) {
                result = applyNamespaces(result, custom_namespaces);
            }
            return deepCopy(result);
        }

        /**
         * Add a type of layout to the registry
         * @param {String} type The type of layout to add (plot, panel, data_layer, toolbar, toolbar_widgets, or tooltip)
         * @param {String} name The name of the layout object to add
         * @param {Object} item The layout object describing parameters
         * @param {boolean} override Whether to replace an existing item by that name
         * @return {*}
         */
        add(type, name, item, override = false) {
            if (!(type && name && item)) {
                throw new Error('To add a layout, type, name, and item must all be specified');
            }
            if (!(typeof item === 'object')) {
                throw new Error('The configuration to be added must be an object');
            }

            if (!this.has(type)) {
                super.add(type, new RegistryBase());
            }
            // Ensure that each use of a layout can be modified, by returning a copy is independent
            const copy = deepCopy(item);

            // Special behavior for datalayers: all registry data layers will attempt to identify the fields requested
            //   from external sources. This is purely a hint, because not every layout is generated through the registry.
            if (type === 'data_layer' && copy.namespace) {
                copy._auto_fields = [...findFields(copy, Object.keys(copy.namespace))].sort();
            }

            return super.get(type).add(name, copy, override);
        }

        /**
         * List all available types of layout (eg toolbar, panel, etc). If a specific type name is provided, list the
         *  layouts for that type of element ("just predefined panels").
         * @param {String} [type] The type of layout (eg toolbar, panel, etc)
         * @return {String[]|Object}
         */
        list(type) {
            if (!type) {
                let result = {};
                for (let [type, contents] of this._items) {
                    result[type] = contents.list();
                }
                return result;
            }
            return super.get(type).list();
        }

        /**
         * Static alias to a helper method. Preserved for backwards compatibility, so that UMD users can access this method.
         * @static
         * @private
         */
        merge(custom_layout, default_layout) {
            return merge(custom_layout, default_layout);
        }

        /**
         * Static alias to a helper method. Allows renaming fields
         * @static
         * @private
         */
        renameField() {
            return renameField(...arguments);
        }

        /**
         * Static alias to a helper method. Allows mutating nested layout attributes
         * @static
         * @private
         */
        mutate_attrs() {
            return mutate_attrs(...arguments);
        }

        /**
         * Static alias to a helper method. Allows mutating nested layout attributes
         * @static
         * @private
         */
        query_attrs() {
            return query_attrs(...arguments);
        }
    }

    /**
     * A plugin registry that allows plots to use both pre-defined and user-provided data adapters.
     * @alias module:LocusZoom~Layouts
     * @type {LayoutRegistry}
     */
    const registry = new LayoutRegistry();

    for (let [type, entries] of Object.entries(layouts)) {
        for (let [name, config] of Object.entries(entries)) {
            registry.add(type, name, config);
        }
    }

    export default registry;

    // Export base class for unit testing
    export {LayoutRegistry as _LayoutRegistry};