import { GestureTool, GestureToolView } from "../gestures/gesture_tool";
import { OnOffButton } from "../on_off_button";
import { BoxAnnotation } from "../../annotations/box_annotation";
import { Range } from "../../ranges/range";
import { logger } from "../../../core/logging";
import { assert, unreachable } from "../../../core/util/assert";
import { isNumber } from "../../../core/util/types";
import { tool_icon_range } from "../../../styles/icons.css";
import { Node } from "../../coordinates/node";
import { Enum } from "../../../core/kinds";
const StartGesture = Enum("pan", "tap", "none");
export class RangeToolView extends GestureToolView {
    static __name__ = "RangeToolView";
    get overlays() {
        return [...super.overlays, this.model.overlay];
    }
    initialize() {
        super.initialize();
        this.model.update_overlay_from_ranges();
    }
    connect_signals() {
        super.connect_signals();
        const update_overlay = () => this.model.update_overlay_from_ranges();
        this.on_transitive_change(this.model.properties.x_range, update_overlay);
        this.on_transitive_change(this.model.properties.y_range, update_overlay);
        this.model.overlay.pan.connect(([state, _]) => {
            if (state == "pan") {
                this.model.update_ranges_from_overlay();
            }
            else if (state == "pan:end") {
                const ranges = [this.model.x_range, this.model.y_range].filter((r) => r != null);
                this.parent.trigger_ranges_update_event(ranges);
            }
        });
        const { active, x_interaction, y_interaction } = this.model.properties;
        this.on_change([active, x_interaction, y_interaction], () => {
            this.model.update_constraints();
        });
    }
    _mappers() {
        const mapper = (units, scale, view, canvas) => {
            switch (units) {
                case "canvas": return canvas;
                case "screen": return view;
                case "data": return scale;
            }
        };
        const { overlay } = this.model;
        const { frame, canvas } = this.plot_view;
        const { x_scale, y_scale } = frame;
        const { x_view, y_view } = frame.bbox;
        const { x_screen, y_screen } = canvas.bbox;
        return {
            left: mapper(overlay.left_units, x_scale, x_view, x_screen),
            right: mapper(overlay.right_units, x_scale, x_view, x_screen),
            top: mapper(overlay.top_units, y_scale, y_view, y_screen),
            bottom: mapper(overlay.bottom_units, y_scale, y_view, y_screen),
        };
    }
    _invert_lrtb({ left, right, top, bottom }) {
        const lrtb = this._mappers();
        const { x_range, y_range } = this.model;
        const has_x = x_range != null;
        const has_y = y_range != null;
        return {
            left: has_x ? lrtb.left.invert(left) : this.model.nodes.left,
            right: has_x ? lrtb.right.invert(right) : this.model.nodes.right,
            top: has_y ? lrtb.top.invert(top) : this.model.nodes.top,
            bottom: has_y ? lrtb.bottom.invert(bottom) : this.model.nodes.bottom,
        };
    }
    _compute_limits(curr_point) {
        const dims = (() => {
            const { x_range, y_range } = this.model;
            const has_x = x_range != null;
            const has_y = y_range != null;
            if (has_x && has_y) {
                return "both";
            }
            else if (has_x) {
                return "width";
            }
            else if (has_y) {
                return "height";
            }
            else {
                unreachable();
            }
        })();
        assert(this._base_point != null);
        let base_point = this._base_point;
        if (this.model.overlay.symmetric) {
            const [cx, cy] = base_point;
            const [dx, dy] = curr_point;
            base_point = [cx - (dx - cx), cy - (dy - cy)];
        }
        const { frame } = this.plot_view;
        return this.model._get_dim_limits(base_point, curr_point, frame, dims);
    }
    _base_point;
    _tap(ev) {
        assert(this.model.start_gesture == "tap");
        const { sx, sy } = ev;
        const { frame } = this.plot_view;
        if (!frame.bbox.contains(sx, sy)) {
            return;
        }
        if (this._base_point == null) {
            this._base_point = [sx, sy];
        }
        else {
            this._update_overlay(sx, sy);
            this._base_point = null;
        }
    }
    _move(ev) {
        if (this._base_point != null && this.model.start_gesture == "tap") {
            const { sx, sy } = ev;
            this._update_overlay(sx, sy);
        }
    }
    _pan_start(ev) {
        assert(this.model.start_gesture == "pan");
        assert(this._base_point == null);
        const { sx, sy } = ev;
        const { frame } = this.plot_view;
        if (!frame.bbox.contains(sx, sy)) {
            return;
        }
        this._base_point = [sx, sy];
    }
    _update_overlay(sx, sy) {
        const [sxlim, sylim] = this._compute_limits([sx, sy]);
        const [[left, right], [top, bottom]] = [sxlim, sylim];
        this.model.overlay.update(this._invert_lrtb({ left, right, top, bottom }));
        this.model.update_ranges_from_overlay();
    }
    _pan(ev) {
        if (this._base_point == null) {
            return;
        }
        const { sx, sy } = ev;
        this._update_overlay(sx, sy);
    }
    _pan_end(ev) {
        if (this._base_point == null) {
            return;
        }
        const { sx, sy } = ev;
        this._update_overlay(sx, sy);
        this._base_point = null;
    }
    get _is_selecting() {
        return this._base_point != null;
    }
    _stop() {
        this._base_point = null;
    }
    _keyup(ev) {
        if (!this.model.active) {
            return;
        }
        if (ev.key == "Escape" && this._is_selecting) {
            this._stop();
        }
    }
}
const DEFAULT_RANGE_OVERLAY = () => {
    return new BoxAnnotation({
        syncable: false,
        level: "overlay",
        visible: true,
        editable: true,
        propagate_hover: true,
        left: NaN,
        right: NaN,
        top: NaN,
        bottom: NaN,
        left_limit: Node.frame.left,
        right_limit: Node.frame.right,
        top_limit: Node.frame.top,
        bottom_limit: Node.frame.bottom,
        fill_color: "lightgrey",
        fill_alpha: 0.5,
        line_color: "black",
        line_alpha: 1.0,
        line_width: 0.5,
        line_dash: [2, 2],
    });
};
export class RangeTool extends GestureTool {
    static __name__ = "RangeTool";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.prototype.default_view = RangeToolView;
        this.define(({ Bool, Ref, Nullable }) => ({
            x_range: [Nullable(Ref(Range)), null],
            y_range: [Nullable(Ref(Range)), null],
            x_interaction: [Bool, true],
            y_interaction: [Bool, true],
            overlay: [Ref(BoxAnnotation), DEFAULT_RANGE_OVERLAY],
            start_gesture: [StartGesture, "none"],
        }));
        this.override({
            active: true,
        });
    }
    initialize() {
        super.initialize();
        this.update_constraints();
    }
    update_constraints() {
        this.overlay.editable = this.active;
        const has_x = this.x_range != null && this.x_interaction;
        const has_y = this.y_range != null && this.y_interaction;
        if (has_x && has_y) {
            this.overlay.movable = "both";
            this.overlay.resizable = "all";
        }
        else if (has_x) {
            this.overlay.movable = "x";
            this.overlay.resizable = "x";
        }
        else if (has_y) {
            this.overlay.movable = "y";
            this.overlay.resizable = "y";
        }
        else {
            this.overlay.movable = "none";
            this.overlay.resizable = "none";
        }
        const { x_range, y_range } = this;
        if (x_range != null) {
            this.overlay.min_width = x_range.min_interval ?? 0;
            this.overlay.max_width = x_range.max_interval ?? Infinity;
        }
        if (y_range != null) {
            this.overlay.min_height = y_range.min_interval ?? 0;
            this.overlay.max_height = y_range.max_interval ?? Infinity;
        }
    }
    update_ranges_from_overlay() {
        const { left, right, top, bottom } = this.overlay;
        const { x_range, y_range } = this;
        const affected_plots = new Set();
        const xrs = new Map();
        const yrs = new Map();
        if (x_range != null && this.x_interaction) {
            assert(isNumber(left) && isNumber(right));
            xrs.set(x_range, { start: left, end: right });
            for (const plot of x_range.linked_plots) {
                affected_plots.add(plot);
            }
        }
        if (y_range != null && this.y_interaction) {
            assert(isNumber(bottom) && isNumber(top));
            yrs.set(y_range, { start: bottom, end: top });
            for (const plot of y_range.linked_plots) {
                affected_plots.add(plot);
            }
        }
        for (const plot of affected_plots) {
            plot.update_range({ xrs, yrs }, { panning: true, scrolling: true });
        }
    }
    nodes = Node.frame.freeze();
    update_overlay_from_ranges() {
        const { x_range, y_range } = this;
        const has_x = x_range != null;
        const has_y = y_range != null;
        this.overlay.update({
            left: has_x ? x_range.start : this.nodes.left,
            right: has_x ? x_range.end : this.nodes.right,
            top: has_y ? y_range.end : this.nodes.top,
            bottom: has_y ? y_range.start : this.nodes.bottom,
        });
        if (!has_x && !has_y) {
            logger.warn("RangeTool not configured with any Ranges.");
            this.overlay.clear();
        }
    }
    tool_name = "Range Tool";
    tool_icon = tool_icon_range;
    get event_type() {
        switch (this.start_gesture) {
            case "pan": return "pan";
            case "tap": return ["tap", "move"];
            case "none": return [];
        }
    }
    default_order = 40;
    supports_auto() {
        return true;
    }
    tool_button() {
        return new OnOffButton({ tool: this });
    }
}
//# sourceMappingURL=range_tool.js.map