import { GestureTool, GestureToolView } from "./gesture_tool";
import { Modifiers, satisfies_modifiers, print_modifiers } from "./common";
import { DataRenderer } from "../../renderers/data_renderer";
import { CompositeScale } from "../../scales/composite_scale";
import { GroupBy } from "../../misc/group_by";
import { scale_range } from "../../../core/util/zoom";
import { Dimensions } from "../../../core/enums";
import { logger } from "../../../core/logging";
import { tool_icon_wheel_zoom } from "../../../styles/icons.css";
import { Enum, List, Ref, Or, Auto } from "../../../core/kinds";
const ZoomTogether = Enum("none", "cross", "all");
const Renderers = Or(List(Ref(DataRenderer)), Auto);
export class WheelZoomToolView extends GestureToolView {
    static __name__ = "WheelZoomToolView";
    _scroll(ev) {
        const { modifiers } = this.model;
        if (!satisfies_modifiers(modifiers, ev.modifiers)) {
            this.plot_view.notify_about(`use ${print_modifiers(modifiers)} + scroll to zoom`);
            return false;
        }
        const { sx, sy, delta } = ev;
        this.zoom(sx, sy, delta);
        return true;
    }
    _pinch(ev) {
        const { sx, sy, scale } = ev;
        const delta = scale >= 1 ? (scale - 1) * 20.0 : -20.0 / scale;
        this.zoom(sx, sy, delta);
    }
    zoom(sx, sy, delta) {
        const axis_view = this.plot_view.axis_views.find((view) => view.bbox.contains(sx, sy));
        if (axis_view != null && !this.model.zoom_on_axis) {
            return;
        }
        const { frame } = this.plot_view;
        if (axis_view == null && !frame.bbox.contains(sx, sy)) {
            return;
        }
        const [x_frame_scales_, y_frame_scales_] = (() => {
            const x_frame = [...frame.x_scales.values()];
            const y_frame = [...frame.y_scales.values()];
            if (axis_view == null) {
                return [x_frame, y_frame];
            }
            else {
                const { zoom_together } = this.model;
                if (zoom_together == "all") {
                    if (axis_view.dimension == 0) {
                        return [x_frame, []];
                    }
                    else {
                        return [[], y_frame];
                    }
                }
                else {
                    const { x_scale, y_scale } = axis_view.coordinates;
                    switch (zoom_together) {
                        case "cross": {
                            return [[x_scale], [y_scale]];
                        }
                        case "none": {
                            if (axis_view.dimension == 0) {
                                return [[x_scale], []];
                            }
                            else {
                                return [[], [y_scale]];
                            }
                        }
                    }
                }
            }
        })();
        const data_renderers = (() => {
            const { renderers } = this.model;
            const data_renderers = new Set(renderers != "auto" ? renderers : this.plot_view.model.data_renderers);
            if (!this.model.hit_test) {
                return data_renderers;
            }
            else {
                const collected_renderers = new Set();
                const hit_renderers = new Set();
                for (const renderer of data_renderers) {
                    if (renderer.coordinates == null) {
                        collected_renderers.add(renderer);
                        continue;
                    }
                    const geometry = (() => {
                        switch (this.model.hit_test_mode) {
                            case "point": return { type: "point", sx, sy };
                            case "hline": return { type: "span", sx, sy, direction: "v" };
                            case "vline": return { type: "span", sx, sy, direction: "h" };
                        }
                    })();
                    const rv = this.plot_view.views.get_one(renderer);
                    const did_hit = rv.hit_test(geometry);
                    if (did_hit != null && !did_hit.is_empty()) {
                        hit_renderers.add(rv.model);
                    }
                }
                if (hit_renderers.size != 0) {
                    const { hit_test_behavior } = this.model;
                    if (hit_test_behavior == "only_hit") {
                        for (const hit of hit_renderers) {
                            collected_renderers.add(hit);
                        }
                    }
                    else {
                        for (const group of hit_test_behavior.query_groups(hit_renderers, data_renderers)) {
                            for (const renderer of group) {
                                if (renderer instanceof DataRenderer && data_renderers.has(renderer)) {
                                    collected_renderers.add(renderer);
                                }
                            }
                        }
                    }
                }
                return [...collected_renderers];
            }
        })();
        const x_frame_scales = new Set(x_frame_scales_);
        const y_frame_scales = new Set(y_frame_scales_);
        const x_renderer_scales = new Set();
        const y_renderer_scales = new Set();
        for (const renderer of data_renderers) {
            if (renderer.coordinates == null) {
                continue;
            }
            const rv = this.plot_view.views.get_one(renderer);
            const { x_scale, y_scale } = rv.coordinates;
            if (x_scale instanceof CompositeScale) {
                if (x_frame_scales.has(x_scale.target_scale)) {
                    x_renderer_scales.add(x_scale);
                }
            }
            if (y_scale instanceof CompositeScale) {
                if (y_frame_scales.has(y_scale.target_scale)) {
                    y_renderer_scales.add(y_scale);
                }
            }
        }
        const [x_all_scales, y_all_scales] = (() => {
            if (this.model.renderers == "auto") {
                return [
                    new Set([...x_frame_scales, ...x_renderer_scales]),
                    new Set([...y_frame_scales, ...y_renderer_scales]),
                ];
            }
            else {
                return [
                    x_renderer_scales,
                    y_renderer_scales,
                ];
            }
        })();
        const subcoord = { x: false, y: false };
        const traverse = (scale, dim) => {
            const { level } = this.model;
            for (let i = 0; i < level; i++) {
                if (scale instanceof CompositeScale) {
                    subcoord[dim] = true;
                    scale = scale.source_scale;
                }
                else {
                    logger.warn(`can't reach sub-coordinate level ${level} for ${scale} in ${dim} dimension; stopped at ${i}`);
                    break;
                }
            }
            if (scale instanceof CompositeScale) {
                return scale.target_scale;
            }
            else {
                return scale;
            }
        };
        const x_scales = new Set();
        const y_scales = new Set();
        for (const x_scale of x_all_scales) {
            x_scales.add(traverse(x_scale, "x"));
        }
        for (const y_scale of y_all_scales) {
            y_scales.add(traverse(y_scale, "y"));
        }
        const center = (() => {
            const x = subcoord.x ? null : sx;
            const y = subcoord.y ? null : sy;
            if (axis_view != null) {
                return axis_view.dimension == 0 ? { x, y: null } : { x: null, y };
            }
            else {
                return { x, y };
            }
        })();
        // restrict to axis configured in tool's dimensions property and if
        // zoom origin is inside of frame range/domain
        const dims = this.model.dimensions;
        const x_axis = dims == "width" || dims == "both";
        const y_axis = dims == "height" || dims == "both";
        const { x_target, y_target } = frame;
        const factor = this.model.speed * delta;
        const zoom_info = scale_range(x_scales, y_scales, x_target, y_target, factor, x_axis, y_axis, center);
        this.plot_view.state.push("wheel_zoom", { range: zoom_info });
        const { maintain_focus } = this.model;
        this.plot_view.update_range(zoom_info, { scrolling: true, maintain_focus });
        this.model.document?.interactive_start(this.plot_view.model, () => this.plot_view.trigger_ranges_update_event());
    }
}
export class WheelZoomTool extends GestureTool {
    static __name__ = "WheelZoomTool";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.prototype.default_view = WheelZoomToolView;
        this.define(({ Bool, Float, NonNegative, Int, Ref, Or }) => ({
            dimensions: [Dimensions, "both"],
            renderers: [Renderers, "auto"],
            level: [NonNegative(Int), 0],
            hit_test: [Bool, false],
            hit_test_mode: [Enum("point", "hline", "vline"), "point"],
            hit_test_behavior: [Or(Ref(GroupBy), Enum("only_hit")), "only_hit"],
            maintain_focus: [Bool, true],
            zoom_on_axis: [Bool, true],
            zoom_together: [ZoomTogether, "all"],
            speed: [Float, 1 / 600],
            modifiers: [Modifiers, {}],
        }));
        this.register_alias("wheel_zoom", () => new WheelZoomTool({ dimensions: "both" }));
        this.register_alias("xwheel_zoom", () => new WheelZoomTool({ dimensions: "width" }));
        this.register_alias("ywheel_zoom", () => new WheelZoomTool({ dimensions: "height" }));
    }
    tool_name = "Wheel Zoom";
    tool_icon = tool_icon_wheel_zoom;
    event_type = "scroll";
    default_order = 10;
    get tooltip() {
        return this._get_dim_tooltip(this.dimensions);
    }
    supports_auto() {
        const { alt, ctrl, shift } = this.modifiers;
        return alt != null || ctrl != null || shift != null;
    }
}
//# sourceMappingURL=wheel_zoom_tool.js.map