import { DataRange1d } from "../ranges/data_range1d";
import { logger } from "../../core/logging";
export class RangeManager {
    constructor(parent) {
        this.parent = parent;
        this.invalidate_dataranges = true;
    }
    get frame() {
        return this.parent.frame;
    }
    update(range_info, options) {
        const { x_ranges, y_ranges } = this.frame;
        if (range_info == null) {
            for (const [, range] of x_ranges) {
                range.reset();
            }
            for (const [, range] of y_ranges) {
                range.reset();
            }
            this.update_dataranges();
        }
        else {
            const range_info_iter = [];
            for (const [name, range] of x_ranges) {
                range_info_iter.push([range, range_info.xrs.get(name)]);
            }
            for (const [name, range] of y_ranges) {
                range_info_iter.push([range, range_info.yrs.get(name)]);
            }
            if (options?.scrolling) {
                this._update_ranges_together(range_info_iter); // apply interval bounds while keeping aspect
            }
            this._update_ranges_individually(range_info_iter, options);
        }
    }
    reset() {
        this.update(null);
    }
    _update_dataranges(frame) {
        // Update any DataRange1ds here
        const bounds = new Map();
        const log_bounds = new Map();
        let calculate_log_bounds = false;
        for (const [, xr] of frame.x_ranges) {
            if (xr instanceof DataRange1d && xr.scale_hint == "log")
                calculate_log_bounds = true;
        }
        for (const [, yr] of frame.y_ranges) {
            if (yr instanceof DataRange1d && yr.scale_hint == "log")
                calculate_log_bounds = true;
        }
        for (const renderer of this.parent.model.data_renderers) {
            const renderer_view = this.parent.renderer_view(renderer);
            if (renderer_view == null)
                continue;
            const bds = renderer_view.glyph_view.bounds();
            if (bds != null)
                bounds.set(renderer, bds);
            if (calculate_log_bounds) {
                const log_bds = renderer_view.glyph_view.log_bounds();
                if (log_bds != null)
                    log_bounds.set(renderer, log_bds);
            }
        }
        let follow_enabled = false;
        let has_bounds = false;
        //const {width, height} = frame.bbox
        const width = frame.x_target.span;
        const height = frame.y_target.span;
        let r;
        if (this.parent.model.match_aspect !== false && width != 0 && height != 0)
            r = (1 / this.parent.model.aspect_scale) * (width / height);
        for (const [, xr] of frame.x_ranges) {
            if (xr instanceof DataRange1d) {
                const bounds_to_use = xr.scale_hint == "log" ? log_bounds : bounds;
                xr.update(bounds_to_use, 0, this.parent.model, r);
                if (xr.follow) {
                    follow_enabled = true;
                }
            }
            if (xr.bounds != null)
                has_bounds = true;
        }
        for (const [, yr] of frame.y_ranges) {
            if (yr instanceof DataRange1d) {
                const bounds_to_use = yr.scale_hint == "log" ? log_bounds : bounds;
                yr.update(bounds_to_use, 1, this.parent.model, r);
                if (yr.follow) {
                    follow_enabled = true;
                }
            }
            if (yr.bounds != null)
                has_bounds = true;
        }
        if (follow_enabled && has_bounds) {
            logger.warn("Follow enabled so bounds are unset.");
            for (const [, xr] of frame.x_ranges) {
                xr.bounds = null;
            }
            for (const [, yr] of frame.y_ranges) {
                yr.bounds = null;
            }
        }
    }
    update_dataranges() {
        this._update_dataranges(this.frame);
        for (const renderer of this.parent.model.renderers) {
            const { coordinates } = renderer;
            if (coordinates != null)
                this._update_dataranges(coordinates);
        }
        if (this.compute_initial() != null)
            this.invalidate_dataranges = false;
    }
    compute_initial() {
        // check for good values for ranges before setting initial range
        let good_vals = true;
        const { x_ranges, y_ranges } = this.frame;
        const xrs = new Map();
        const yrs = new Map();
        for (const [name, range] of x_ranges) {
            const { start, end } = range;
            if (start == null || end == null || isNaN(start + end)) {
                good_vals = false;
                break;
            }
            xrs.set(name, { start, end });
        }
        if (good_vals) {
            for (const [name, range] of y_ranges) {
                const { start, end } = range;
                if (start == null || end == null || isNaN(start + end)) {
                    good_vals = false;
                    break;
                }
                yrs.set(name, { start, end });
            }
        }
        if (good_vals)
            return { xrs, yrs };
        else {
            logger.warn("could not set initial ranges");
            return null;
        }
    }
    _update_ranges_together(range_info_iter) {
        // Get weight needed to scale the diff of the range to honor interval limits
        let weight = 1.0;
        for (const [rng, range_info] of range_info_iter) {
            weight = Math.min(weight, this._get_weight_to_constrain_interval(rng, range_info));
        }
        // Apply shared weight to all ranges
        if (weight < 1) {
            for (const [rng, range_info] of range_info_iter) {
                range_info.start = weight * range_info.start + (1 - weight) * rng.start;
                range_info.end = weight * range_info.end + (1 - weight) * rng.end;
            }
        }
    }
    _update_ranges_individually(range_info_iter, options) {
        const panning = !!options?.panning;
        const scrolling = !!options?.scrolling;
        let hit_bound = false;
        for (const [rng, range_info] of range_info_iter) {
            // Limit range interval first. Note that for scroll events,
            // the interval has already been limited for all ranges simultaneously
            if (!scrolling) {
                const weight = this._get_weight_to_constrain_interval(rng, range_info);
                if (weight < 1) {
                    range_info.start = weight * range_info.start + (1 - weight) * rng.start;
                    range_info.end = weight * range_info.end + (1 - weight) * rng.end;
                }
            }
            // Prevent range from going outside limits
            // Also ensure that range keeps the same delta when panning/scrolling
            if (rng.bounds != null && rng.bounds != "auto") { // check `auto` for type-checking purpose
                const [min, max] = rng.bounds;
                const new_interval = Math.abs(range_info.end - range_info.start);
                if (rng.is_reversed) {
                    if (min != null) {
                        if (min > range_info.end) {
                            hit_bound = true;
                            range_info.end = min;
                            if (panning || scrolling) {
                                range_info.start = min + new_interval;
                            }
                        }
                    }
                    if (max != null) {
                        if (max < range_info.start) {
                            hit_bound = true;
                            range_info.start = max;
                            if (panning || scrolling) {
                                range_info.end = max - new_interval;
                            }
                        }
                    }
                }
                else {
                    if (min != null) {
                        if (min > range_info.start) {
                            hit_bound = true;
                            range_info.start = min;
                            if (panning || scrolling) {
                                range_info.end = min + new_interval;
                            }
                        }
                    }
                    if (max != null) {
                        if (max < range_info.end) {
                            hit_bound = true;
                            range_info.end = max;
                            if (panning || scrolling) {
                                range_info.start = max - new_interval;
                            }
                        }
                    }
                }
            }
        }
        // Cancel the event when hitting a bound while scrolling. This ensures that
        // the scroll-zoom tool maintains its focus position. Setting `maintain_focus`
        // to false results in a more "gliding" behavior, allowing one to
        // zoom out more smoothly, at the cost of losing the focus position.
        if (scrolling && hit_bound && options?.maintain_focus)
            return;
        for (const [rng, range_info] of range_info_iter) {
            rng.have_updated_interactively = true;
            if (rng.start != range_info.start || rng.end != range_info.end)
                rng.setv(range_info);
        }
    }
    _get_weight_to_constrain_interval(rng, range_info) {
        // Get the weight by which a range-update can be applied
        // to still honor the interval limits (including the implicit
        // max interval imposed by the bounds)
        const { min_interval } = rng;
        let { max_interval } = rng;
        // Express bounds as a max_interval. By doing this, the application of
        // bounds and interval limits can be applied independent from each-other.
        if (rng.bounds != null && rng.bounds != "auto") { // check `auto` for type-checking purpose
            const [min, max] = rng.bounds;
            if (min != null && max != null) {
                const max_interval2 = Math.abs(max - min);
                max_interval = max_interval != null ? Math.min(max_interval, max_interval2) : max_interval2;
            }
        }
        let weight = 1.0;
        if (min_interval != null || max_interval != null) {
            const old_interval = Math.abs(rng.end - rng.start);
            const new_interval = Math.abs(range_info.end - range_info.start);
            if (min_interval != null && min_interval > 0 && new_interval < min_interval) {
                weight = (old_interval - min_interval) / (old_interval - new_interval);
            }
            if (max_interval != null && max_interval > 0 && new_interval > max_interval) {
                weight = (max_interval - old_interval) / (new_interval - old_interval);
            }
            weight = Math.max(0.0, Math.min(1.0, weight));
        }
        return weight;
    }
}
RangeManager.__name__ = "RangeManager";
//# sourceMappingURL=range_manager.js.map