import { UIGestures } from "./ui_gestures";
import { Signal } from "./signaling";
import { offset_bbox } from "./dom";
import * as events from "./bokeh_events";
import { getDeltaY } from "./util/wheel";
import { reversed, is_empty } from "./util/array";
import { isObject, isBoolean } from "./util/types";
import { ToolProxy } from "../models/tools/tool_proxy";
export function is_Tapable(obj) {
    return isObject(obj) && "on_tap" in obj;
}
export function is_Moveable(obj) {
    return isObject(obj) && "on_enter" in obj && "on_move" in obj && "on_leave" in obj;
}
export function is_Pannable(obj) {
    return isObject(obj) && "on_pan_start" in obj && "on_pan" in obj && "on_pan_end" in obj;
}
export function is_Pinchable(obj) {
    return isObject(obj) && "on_pinch_start" in obj && "on_pinch" in obj && "on_pinch_end" in obj;
}
export function is_Rotatable(obj) {
    return isObject(obj) && "on_rotate_start" in obj && "on_rotate" in obj && "on_rotate_end" in obj;
}
export function is_Scrollable(obj) {
    return isObject(obj) && "on_scroll" in obj;
}
export function is_Keyable(obj) {
    return isObject(obj) && "on_keydown" in obj && "on_keyup" in obj;
}
export class UIEventBus {
    canvas_view;
    static __name__ = "UIEventBus";
    pan_start = new Signal(this, "pan:start");
    pan = new Signal(this, "pan");
    pan_end = new Signal(this, "pan:end");
    pinch_start = new Signal(this, "pinch:start");
    pinch = new Signal(this, "pinch");
    pinch_end = new Signal(this, "pinch:end");
    rotate_start = new Signal(this, "rotate:start");
    rotate = new Signal(this, "rotate");
    rotate_end = new Signal(this, "rotate:end");
    tap = new Signal(this, "tap");
    doubletap = new Signal(this, "doubletap");
    press = new Signal(this, "press");
    pressup = new Signal(this, "pressup");
    move_enter = new Signal(this, "move:enter");
    move = new Signal(this, "move");
    move_exit = new Signal(this, "move:exit");
    scroll = new Signal(this, "scroll");
    keydown = new Signal(this, "keydown");
    keyup = new Signal(this, "keyup");
    hit_area;
    ui_gestures;
    constructor(canvas_view) {
        this.canvas_view = canvas_view;
        this.hit_area = canvas_view.events_el;
        this.on_tap = this.on_tap.bind(this);
        this.on_doubletap = this.on_doubletap.bind(this);
        this.on_press = this.on_press.bind(this);
        this.on_pressup = this.on_pressup.bind(this);
        this.on_enter = this.on_enter.bind(this);
        this.on_move = this.on_move.bind(this);
        this.on_leave = this.on_leave.bind(this);
        this.on_pan_start = this.on_pan_start.bind(this);
        this.on_pan = this.on_pan.bind(this);
        this.on_pan_end = this.on_pan_end.bind(this);
        this.on_pinch_start = this.on_pinch_start.bind(this);
        this.on_pinch = this.on_pinch.bind(this);
        this.on_pinch_end = this.on_pinch_end.bind(this);
        this.on_rotate_start = this.on_rotate_start.bind(this);
        this.on_rotate = this.on_rotate.bind(this);
        this.on_rotate_end = this.on_rotate_end.bind(this);
        this.ui_gestures = new UIGestures(this.hit_area, this, { must_be_target: true });
        this.ui_gestures.connect_signals();
        this.on_context_menu = this.on_context_menu.bind(this);
        this.on_mouse_wheel = this.on_mouse_wheel.bind(this);
        this.on_key_down = this.on_key_down.bind(this);
        this.on_key_up = this.on_key_up.bind(this);
        this.hit_area.addEventListener("contextmenu", this.on_context_menu);
        this.hit_area.addEventListener("wheel", this.on_mouse_wheel);
        document.addEventListener("keydown", this.on_key_down);
        document.addEventListener("keyup", this.on_key_up);
    }
    remove() {
        this.ui_gestures.remove();
        this.hit_area.removeEventListener("contextmenu", this.on_context_menu);
        this.hit_area.removeEventListener("wheel", this.on_mouse_wheel);
        document.removeEventListener("keydown", this.on_key_down);
        document.removeEventListener("keyup", this.on_key_up);
    }
    _tools = new Map();
    register_tool(tool_view) {
        const { model: tool } = tool_view;
        if (this._tools.has(tool)) {
            throw new Error(`${tool} already registered`);
        }
        else {
            this._tools.set(tool, tool_view);
        }
    }
    hit_test_renderers(plot_view, sx, sy) {
        const collected = [];
        for (const view of reversed(plot_view.all_renderer_views)) {
            if (view.interactive_hit?.(sx, sy) ?? false) {
                collected.push(view);
            }
        }
        return collected;
    }
    set_cursor(cursor) {
        this.hit_area.style.cursor = cursor ?? "default";
    }
    hit_test_frame(plot_view, sx, sy) {
        return plot_view.frame.bbox.contains(sx, sy);
    }
    hit_test_plot(sx, sy) {
        // TODO: z-index
        for (const plot_view of this.canvas_view.plot_views) {
            if (plot_view.bbox.relative() /*XXX*/.contains(sx, sy)) {
                return plot_view;
            }
        }
        return null;
    }
    _prev_move = null;
    _curr_pan = null;
    _curr_pinch = null;
    _curr_rotate = null;
    _trigger(signal, e) {
        if (!this.hit_area.isConnected) {
            return;
        }
        const { sx, sy, native: srcEvent } = e;
        const plot_view = this.hit_test_plot(sx, sy);
        const curr_view = plot_view;
        const relativize_event = (_plot_view) => {
            const [rel_sx, rel_sy] = [sx, sy]; // plot_view.layout.bbox.relativize(sx, sy)
            return { ...e, sx: rel_sx, sy: rel_sy };
        };
        if (e.type == "pan_start" || e.type == "pan" || e.type == "pan_end") {
            let pan_view;
            if (e.type == "pan_start" && curr_view != null) {
                this._curr_pan = { plot_view: curr_view };
                pan_view = curr_view;
            }
            else if (e.type == "pan" && this._curr_pan != null) {
                pan_view = this._curr_pan.plot_view;
            }
            else if (e.type == "pan_end" && this._curr_pan != null) {
                pan_view = this._curr_pan.plot_view;
                this._curr_pan = null;
            }
            else {
                pan_view = null;
            }
            if (pan_view != null) {
                const event = relativize_event(pan_view);
                this.__trigger(pan_view, signal, event, srcEvent);
            }
        }
        else if (e.type == "pinch_start" || e.type == "pinch" || e.type == "pinch_end") {
            let pinch_view;
            if (e.type == "pinch_start" && curr_view != null) {
                this._curr_pinch = { plot_view: curr_view };
                pinch_view = curr_view;
            }
            else if (e.type == "pinch" && this._curr_pinch != null) {
                pinch_view = this._curr_pinch.plot_view;
            }
            else if (e.type == "pinch_end" && this._curr_pinch != null) {
                pinch_view = this._curr_pinch.plot_view;
                this._curr_pinch = null;
            }
            else {
                pinch_view = null;
            }
            if (pinch_view != null) {
                const event = relativize_event(pinch_view);
                this.__trigger(pinch_view, signal, event, srcEvent);
            }
        }
        else if (e.type == "rotate_start" || e.type == "rotate" || e.type == "rotate_end") {
            let rotate_view;
            if (e.type == "rotate_start" && curr_view != null) {
                this._curr_rotate = { plot_view: curr_view };
                rotate_view = curr_view;
            }
            else if (e.type == "rotate" && this._curr_rotate != null) {
                rotate_view = this._curr_rotate.plot_view;
            }
            else if (e.type == "rotate_end" && this._curr_rotate != null) {
                rotate_view = this._curr_rotate.plot_view;
                this._curr_rotate = null;
            }
            else {
                rotate_view = null;
            }
            if (rotate_view != null) {
                const event = relativize_event(rotate_view);
                this.__trigger(rotate_view, signal, event, srcEvent);
            }
        }
        else if (e.type == "enter" || e.type == "move" || e.type == "leave") {
            const prev_view = this._prev_move?.plot_view;
            if (prev_view != null && (e.type == "leave" || prev_view != curr_view)) {
                const { sx, sy } = relativize_event(prev_view);
                this.__trigger(prev_view, this.move_exit, { type: "leave", sx, sy, modifiers: { shift: false, ctrl: false, alt: false }, native: srcEvent }, srcEvent);
            }
            if (curr_view != null && (e.type == "enter" || prev_view != curr_view)) {
                const { sx, sy } = relativize_event(curr_view);
                this.__trigger(curr_view, this.move_enter, { type: "enter", sx, sy, modifiers: { shift: false, ctrl: false, alt: false }, native: srcEvent }, srcEvent);
            }
            if (curr_view != null && e.type == "move") {
                const event = relativize_event(curr_view);
                this.__trigger(curr_view, signal, event, srcEvent);
            }
            this._prev_move = { sx, sy, plot_view: curr_view };
        }
        else {
            if (curr_view != null) {
                const event = relativize_event(curr_view);
                this.__trigger(curr_view, signal, event, srcEvent);
            }
        }
    }
    _current_pan_view = null;
    _current_pinch_view = null;
    _current_rotate_view = null;
    _current_move_views = [];
    __trigger(plot_view, signal, e, srcEvent) {
        const gestures = plot_view.model.toolbar.gestures;
        const event_type = signal.name;
        const base_type = event_type.split(":")[0];
        const views = this.hit_test_renderers(plot_view, e.sx, e.sy);
        if (base_type == "pan") {
            const event = e;
            if (this._current_pan_view == null) {
                if (event_type == "pan:start") {
                    for (const view of views) {
                        if (!is_Pannable(view)) {
                            continue;
                        }
                        if (view.on_pan_start(event)) {
                            this._current_pan_view = view;
                            srcEvent.preventDefault();
                            return;
                        }
                    }
                }
            }
            else {
                if (event_type == "pan") {
                    this._current_pan_view.on_pan(event);
                }
                else if (event_type == "pan:end") {
                    this._current_pan_view.on_pan_end(event);
                    this._current_pan_view = null;
                }
                srcEvent.preventDefault();
                return;
            }
        }
        else if (base_type == "pinch") {
            const event = e;
            if (this._current_pinch_view == null) {
                if (event_type == "pinch:start") {
                    for (const view of views) {
                        if (!is_Pinchable(view)) {
                            continue;
                        }
                        if (view.on_pinch_start(event)) {
                            this._current_pinch_view = view;
                            srcEvent.preventDefault();
                            return;
                        }
                    }
                }
            }
            else {
                if (event_type == "pinch") {
                    this._current_pinch_view.on_pinch(event);
                }
                else if (event_type == "pinch:end") {
                    this._current_pinch_view.on_pinch_end(event);
                    this._current_pinch_view = null;
                }
                srcEvent.preventDefault();
                return;
            }
        }
        else if (base_type == "rotate") {
            const event = e;
            if (this._current_rotate_view == null) {
                if (event_type == "rotate:start") {
                    for (const view of views) {
                        if (!is_Rotatable(view)) {
                            continue;
                        }
                        if (view.on_rotate_start(event)) {
                            this._current_rotate_view = view;
                            srcEvent.preventDefault();
                            return;
                        }
                    }
                }
            }
            else {
                if (event_type == "rotate") {
                    this._current_rotate_view.on_rotate(event);
                }
                else if (event_type == "rotate:end") {
                    this._current_rotate_view.on_rotate_end(event);
                    this._current_rotate_view = null;
                }
                srcEvent.preventDefault();
                return;
            }
        }
        else if (base_type == "move") {
            const event = e;
            const new_views = new Set(views);
            const current_views = new Set(this._current_move_views);
            this._current_move_views = [];
            for (const view of current_views) {
                if (!new_views.has(view)) {
                    current_views.delete(view);
                    view.on_leave(event);
                }
            }
            for (const view of views) {
                if (!is_Moveable(view)) {
                    continue;
                }
                if (!current_views.has(view)) {
                    if (view.on_enter(e)) {
                        this._current_move_views.push(view);
                    }
                }
                else {
                    this._current_move_views.push(view);
                    view.on_move(event);
                }
            }
        }
        function get_tool_view(tool_like) {
            if (tool_like != null) {
                const tool = tool_like instanceof ToolProxy ? tool_like.tools[0] : tool_like;
                return plot_view.tool_views.get(tool) ?? null;
            }
            else {
                return null;
            }
        }
        const top_view = views.at(0);
        switch (base_type) {
            case "move": {
                const active_gesture = gestures.move.active;
                if (active_gesture != null) {
                    this.trigger(signal, e, active_gesture);
                }
                const active_inspectors = plot_view.model.toolbar.inspectors.filter(t => t.active);
                const cursor = (() => {
                    const current_view = this._current_pan_view ??
                        this._current_pinch_view ??
                        this._current_rotate_view ??
                        this._current_move_views.at(0) ??
                        top_view ??
                        get_tool_view(active_gesture);
                    if (current_view != null) {
                        const cursor = current_view.cursor(e.sx, e.sy);
                        if (cursor != null) {
                            return cursor;
                        }
                    }
                    if (this.hit_test_frame(plot_view, e.sx, e.sy) && !is_empty(active_inspectors)) {
                        // the event happened on the plot frame but off a renderer
                        return "crosshair";
                    }
                    return null;
                })();
                this.set_cursor(cursor);
                if (top_view != null && !top_view.model.propagate_hover && !is_empty(active_inspectors)) {
                    // override event_type to cause inspectors to clear overlays
                    signal = this.move_exit; // XXX
                }
                active_inspectors.map((inspector) => this.trigger(signal, e, inspector));
                break;
            }
            case "tap": {
                const path = srcEvent.composedPath();
                if (path.length != 0 && path[0] != this.hit_area) {
                    return; // don't trigger bokeh events
                }
                top_view?.on_hit?.(e.sx, e.sy);
                if (this.hit_test_frame(plot_view, e.sx, e.sy)) {
                    const active_gesture = gestures.tap.active;
                    if (active_gesture != null) {
                        this.trigger(signal, e, active_gesture);
                    }
                }
                break;
            }
            case "doubletap": {
                if (this.hit_test_frame(plot_view, e.sx, e.sy)) {
                    const active_gesture = gestures.doubletap.active ?? gestures.tap.active;
                    if (active_gesture != null) {
                        this.trigger(signal, e, active_gesture);
                    }
                }
                break;
            }
            case "press": {
                if (this.hit_test_frame(plot_view, e.sx, e.sy)) {
                    const active_gesture = gestures.press.active ?? gestures.tap.active;
                    if (active_gesture != null) {
                        this.trigger(signal, e, active_gesture);
                    }
                }
                break;
            }
            case "pinch": {
                const active_gesture = gestures.pinch.active ?? gestures.scroll.active;
                if (active_gesture != null) {
                    if (this.trigger(signal, e, active_gesture)) {
                        srcEvent.preventDefault();
                        srcEvent.stopPropagation();
                    }
                }
                break;
            }
            case "scroll": {
                const active_gesture = gestures.scroll.active;
                if (active_gesture != null) {
                    if (this.trigger(signal, e, active_gesture)) {
                        srcEvent.preventDefault();
                        srcEvent.stopPropagation();
                    }
                }
                break;
            }
            case "pan": {
                const active_gesture = gestures.pan.active;
                if (active_gesture != null) {
                    if (this.trigger(signal, e, active_gesture)) {
                        srcEvent.preventDefault();
                        srcEvent.stopPropagation();
                    }
                }
                /* TODO this requires knowledge of the current interactive
                        tool (similar to _current_pan_view, etc.)
                const active_pan_view = get_tool_view(active_gesture)
                if (active_pan_view != null) {
                  const cursor = active_pan_view.cursor(e.sx, e.sy)
                  this.set_cursor(cursor)
                }
                */
                break;
            }
            default: {
                const active_gesture = gestures[base_type].active;
                if (active_gesture != null) {
                    this.trigger(signal, e, active_gesture);
                }
            }
        }
        this._trigger_bokeh_event(plot_view, e);
    }
    trigger(signal, e, tool = null) {
        const emit = (tool) => {
            const tool_view = this._tools.get(tool);
            if (tool_view == null) {
                return false;
            }
            const fn = (() => {
                switch (signal) {
                    case this.pan_start: return tool_view._pan_start;
                    case this.pan: return tool_view._pan;
                    case this.pan_end: return tool_view._pan_end;
                    case this.pinch_start: return tool_view._pinch_start;
                    case this.pinch: return tool_view._pinch;
                    case this.pinch_end: return tool_view._pinch_end;
                    case this.rotate_start: return tool_view._rotate_start;
                    case this.rotate: return tool_view._rotate;
                    case this.rotate_end: return tool_view._rotate_end;
                    case this.move_enter: return tool_view._move_enter;
                    case this.move: return tool_view._move;
                    case this.move_exit: return tool_view._move_exit;
                    case this.tap: return tool_view._tap;
                    case this.doubletap: return tool_view._doubletap;
                    case this.press: return tool_view._press;
                    case this.pressup: return tool_view._pressup;
                    case this.scroll: return tool_view._scroll;
                    case this.keydown: return tool_view._keydown;
                    case this.keyup: return tool_view._keyup;
                    default: return null;
                }
            })();
            if (fn == null) {
                return false;
            }
            const val = fn.bind(tool_view)(e);
            if (isBoolean(val)) {
                return val;
            }
            else {
                return true;
            }
        };
        if (tool != null) {
            return emit(tool);
        }
        else {
            let result = false;
            for (const tool of this._tools.keys()) {
                // don't conflate these lines because of short circuiting nature of ||= operator
                const emitted = emit(tool);
                result ||= emitted;
            }
            return result;
        }
    }
    /*protected*/ _trigger_bokeh_event(plot_view, ev) {
        const bokeh_event = (() => {
            const { sx, sy, modifiers } = ev;
            const x = plot_view.frame.x_scale.invert(sx);
            const y = plot_view.frame.y_scale.invert(sy);
            switch (ev.type) {
                case "wheel": return new events.MouseWheel(sx, sy, x, y, ev.delta, modifiers);
                case "enter": return new events.MouseEnter(sx, sy, x, y, modifiers);
                case "move": return new events.MouseMove(sx, sy, x, y, modifiers);
                case "leave": return new events.MouseLeave(sx, sy, x, y, modifiers);
                case "tap": return new events.Tap(sx, sy, x, y, modifiers);
                case "double_tap": return new events.DoubleTap(sx, sy, x, y, modifiers);
                case "press": return new events.Press(sx, sy, x, y, modifiers);
                case "press_up": return new events.PressUp(sx, sy, x, y, modifiers);
                case "pan_start": return new events.PanStart(sx, sy, x, y, modifiers);
                case "pan": return new events.Pan(sx, sy, x, y, ev.dx, ev.dy, modifiers);
                case "pan_end": return new events.PanEnd(sx, sy, x, y, modifiers);
                case "pinch_start": return new events.PinchStart(sx, sy, x, y, modifiers);
                case "pinch": return new events.Pinch(sx, sy, x, y, ev.scale, modifiers);
                case "pinch_end": return new events.PinchEnd(sx, sy, x, y, modifiers);
                case "rotate_start": return new events.RotateStart(sx, sy, x, y, modifiers);
                case "rotate": return new events.Rotate(sx, sy, x, y, ev.rotation, modifiers);
                case "rotate_end": return new events.RotateEnd(sx, sy, x, y, modifiers);
                default: return null;
            }
        })();
        if (bokeh_event != null) {
            plot_view.model.trigger_event(bokeh_event);
        }
    }
    _get_sxy(event) {
        const { pageX, pageY } = event;
        const { left, top } = offset_bbox(this.hit_area);
        return {
            sx: pageX - left,
            sy: pageY - top,
        };
    }
    _get_modifiers(event) {
        return {
            shift: event.shiftKey,
            ctrl: event.ctrlKey,
            alt: event.altKey,
        };
    }
    _scroll_event(event) {
        return {
            type: event.type,
            ...this._get_sxy(event),
            delta: getDeltaY(event),
            modifiers: this._get_modifiers(event),
            native: event,
        };
    }
    _key_event(event) {
        return {
            type: event.type,
            key: event.key,
            modifiers: this._get_modifiers(event),
            native: event,
        };
    }
    on_tap(event) {
        this._trigger(this.tap, event);
    }
    on_doubletap(event) {
        this._trigger(this.doubletap, event);
    }
    on_press(event) {
        this._trigger(this.press, event);
    }
    on_pressup(event) {
        this._trigger(this.pressup, event);
    }
    on_enter(event) {
        this._trigger(this.move_enter, event);
    }
    on_move(event) {
        this._trigger(this.move, event);
    }
    on_leave(event) {
        this._trigger(this.move_exit, event);
    }
    on_pan_start(event) {
        this._trigger(this.pan_start, event);
    }
    on_pan(event) {
        this._trigger(this.pan, event);
    }
    on_pan_end(event) {
        this._trigger(this.pan_end, event);
    }
    on_pinch_start(event) {
        this._trigger(this.pinch_start, event);
    }
    on_pinch(event) {
        this._trigger(this.pinch, event);
    }
    on_pinch_end(event) {
        this._trigger(this.pinch_end, event);
    }
    on_rotate_start(event) {
        this._trigger(this.rotate_start, event);
    }
    on_rotate(event) {
        this._trigger(this.rotate, event);
    }
    on_rotate_end(event) {
        this._trigger(this.rotate_end, event);
    }
    on_mouse_wheel(event) {
        this._trigger(this.scroll, this._scroll_event(event));
    }
    on_context_menu(_event) {
        // TODO
    }
    on_key_down(event) {
        // NOTE: keyup event triggered unconditionally
        this.trigger(this.keydown, this._key_event(event));
    }
    on_key_up(event) {
        // NOTE: keyup event triggered unconditionally
        this.trigger(this.keyup, this._key_event(event));
    }
}
//# sourceMappingURL=ui_events.js.map