# Source: components/data_layer/highlight_regions.js

    import * as d3 from 'd3';

    import BaseDataLayer from './base';
    import {merge} from '../../helpers/layouts';

    /**
     * @memberof module:LocusZoom_DataLayers~highlight_regions
     */
    const default_layout = {
        color: '#CCCCCC',
        fill_opacity: 0.5,
        // By default, it will draw the regions shown.
        filters: null,
        // Most use cases will show a preset list of regions defined in the layout
        //  (if empty, AND layout.fields is not, it could fetch from a data source instead)
        regions: [],
        id_field: 'id',
        start_field: 'start',
        end_field: 'end',
        merge_field: null,
    };

    /**
     * "Highlight regions with rectangle" data layer.
     * Creates one (or more) continuous 2D rectangles that mark an entire interval, to the full height of the panel.
     *
     * Each individual rectangle can be shown in full, or overlapping ones can be merged (eg, based on same category).
     * The rectangles are generally drawn with partial transparency, and do not respond to mouse events: they are a
     *   useful highlight tool to draw attention to intervals that contain interesting variants.
     *
     * This layer has several useful modes:
     * 1. Draw one or more specified rectangles as provided from:
     *      A. Hard-coded layout (layout.regions)
     *      B. Data fetched from a source (like intervals with start and end coordinates)- as specified in layout.fields
     * 2. Fetch data from an external source, and only render the intervals that match criteria
     *
     * @alias module:LocusZoom_DataLayers~highlight_regions
     * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options
     */
    class HighlightRegions extends BaseDataLayer {
        /**
         * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.color='#CCCCCC'] The fill color for each rectangle
         * @param {String|module:LocusZoom_DataLayers~ScalableParameter[]} [layout.fill_opacity=0.5] The opacity (0-1). We recommend partial transparency so that
         *   rectangles do not hide or interfere with adjacent elements.
         * @param {Object[]} [layout.filters] An array of filter entries specifying which intervals to draw annotations for.
         * @param {Object[]} [layout.regions] A hard-coded list of regions. If provided, takes precedence over data fetched from an external source.
         * @param {String} [layout.start_field='start'] The field to use for rectangle start x coordinate
         * @param {String} [layout.end_field='end'] The field to use for rectangle end x coordinate
         * @param {String} [layout.merge_field] If two intervals overlap, they can be "merged" based on a field that
         *  identifies the category (eg, only rectangles of the same category will be merged).
         *  This field must be present in order to trigger merge behavior. This is applied after filters.
         */
        constructor(layout) {
            merge(layout, default_layout);
            if (layout.interaction || layout.behaviors) {
                throw new Error('highlight_regions layer does not support mouse events');
            }

            if (layout.regions.length && layout.namespace && Object.keys(layout.namespace).length) {
                throw new Error('highlight_regions layer can specify "regions" in layout, OR external data "fields", but not both');
            }
            super(...arguments);
        }

        /**
         * Helper method that combines two rectangles if they are the same type of data (category) and occupy the same
         *  area of the plot (will automatically sort the data prior to rendering)
         *
         * When two fields conflict, it will fill in the fields for the last of the items that overlap in that range.
         *   Thus, it is not recommended to use tooltips with this feature, because the tooltip won't reflect real data.
         * @param {Object[]} data
         * @return {Object[]}
         * @private
         */
        _mergeNodes(data) {
            const { end_field, merge_field, start_field } = this.layout;
            if (!merge_field) {
                return data;
            }

            // Ensure data is sorted by start field, with category as a tie breaker
            data.sort((a, b) => {
                // Ensure that data is sorted by category, then start field (ensures overlapping intervals are adjacent)
                return d3.ascending(a[merge_field], b[merge_field]) || d3.ascending(a[start_field], b[start_field]);
            });

            let track_data = [];
            data.forEach(function (cur_item, index) {
                const prev_item = track_data[track_data.length - 1] || cur_item;
                if (cur_item[merge_field] === prev_item[merge_field] && cur_item[start_field] <= prev_item[end_field]) {
                    // If intervals overlap, merge the current item with the previous, and append only the merged interval
                    const new_start = Math.min(prev_item[start_field], cur_item[start_field]);
                    const new_end = Math.max(prev_item[end_field], cur_item[end_field]);
                    cur_item = Object.assign({}, prev_item, cur_item, { [start_field]: new_start, [end_field]: new_end });
                    track_data.pop();
                }
                track_data.push(cur_item);
            });
            return track_data;
        }

        render() {
            const { x_scale } = this.parent;
            // Apply filters to only render a specified set of points
            let track_data = this.layout.regions.length ? this.layout.regions : this.data;

            // Pseudo identifier for internal use only (regions have no semantic or transition meaning)
            track_data.forEach((d, i) => d.id || (d.id = i));
            track_data = this._applyFilters(track_data);
            track_data = this._mergeNodes(track_data);

            const selection = this.svg.group.selectAll(`rect.lz-data_layer-${this.layout.type}`)
                .data(track_data);

            // Draw rectangles
            selection.enter()
                .append('rect')
                .attr('class', `lz-data_layer-${this.layout.type}`)
                .merge(selection)
                .attr('id', (d) => this.getElementId(d))
                .attr('x', (d) => x_scale(d[this.layout.start_field]))
                .attr('width', (d) => x_scale(d[this.layout.end_field]) - x_scale(d[this.layout.start_field]))
                .attr('height', this.parent.layout.height)
                .attr('fill', (d, i) => this.resolveScalableParameter(this.layout.color, d, i))
                .attr('fill-opacity', (d, i) => this.resolveScalableParameter(this.layout.fill_opacity, d, i));

            // Remove unused elements
            selection.exit()
                .remove();

            // Note: This layer intentionally does not allow tooltips or mouse behaviors, and doesn't affect pan/zoom
            this.svg.group.style('pointer-events', 'none');
        }

        _getTooltipPosition(tooltip) {
            // This layer is for visual highlighting only; it does not allow mouse interaction, drag, or tooltips
            throw new Error('This layer does not support tooltips');
        }
    }

    export {HighlightRegions as default};