import { logger } from "../../core/logging";
import { div, a } from "../../core/dom";
import { build_views, remove_views } from "../../core/build_views";
import { UIElement, UIElementView } from "../ui/ui_element";
import { Logo, Location } from "../../core/enums";
import { every, sort_by, includes, intersection, split, clear } from "../../core/util/array";
import { join } from "../../core/util/iterator";
import { typed_keys, values, entries } from "../../core/util/object";
import { isArray } from "../../core/util/types";
import { Tool } from "./tool";
import { ToolProxy } from "./tool_proxy";
import { ToolButton } from "./tool_button";
import { GestureTool } from "./gestures/gesture_tool";
import { InspectTool } from "./inspectors/inspect_tool";
import { ActionTool } from "./actions/action_tool";
import { HelpTool } from "./actions/help_tool";
import { ContextMenu } from "../../core/util/menus";
import toolbars_css, * as toolbars from "../../styles/toolbar.css";
import logos_css, * as logos from "../../styles/logo.css";
import icons_css from "../../styles/icons.css";
export class ToolbarView extends UIElementView {
    static __name__ = "ToolbarView";
    _tool_button_views = new Map();
    _tool_buttons;
    _items = [];
    get tool_buttons() {
        return this._tool_buttons.flat();
    }
    _overflow_menu;
    _overflow_el;
    get overflow_el() {
        return this._overflow_el;
    }
    _visible = null;
    get visible() {
        return !this.model.visible ? false : (!this.model.autohide || (this._visible ?? false));
    }
    *children() {
        yield* super.children();
        yield* this._tool_button_views.values();
    }
    has_finished() {
        if (!super.has_finished()) {
            return false;
        }
        for (const child_view of this._tool_button_views.values()) {
            if (!child_view.has_finished()) {
                return false;
            }
        }
        return true;
    }
    initialize() {
        super.initialize();
        const { location } = this.model;
        const reversed = location == "left" || location == "above";
        const orientation = this.model.horizontal ? "vertical" : "horizontal";
        this._overflow_menu = new ContextMenu([], {
            target: this.el,
            orientation,
            reversed,
            prevent_hide: (event) => {
                return event.composedPath().includes(this._overflow_el);
            },
        });
    }
    async lazy_initialize() {
        await super.lazy_initialize();
        await this._build_tool_button_views();
    }
    connect_signals() {
        super.connect_signals();
        const { buttons, tools, location, autohide } = this.model.properties;
        this.on_change([buttons, tools], async () => {
            await this._build_tool_button_views();
            this.render();
        });
        this.on_change(location, () => {
            this.render();
        });
        this.on_change(autohide, () => {
            this._on_visible_change();
        });
    }
    stylesheets() {
        return [...super.stylesheets(), toolbars_css, logos_css, icons_css];
    }
    remove() {
        remove_views(this._tool_button_views);
        super.remove();
    }
    async _build_tool_button_views() {
        this._tool_buttons = (() => {
            const { buttons } = this.model;
            if (buttons == "auto") {
                const groups = [
                    ...values(this.model.gestures).map((gesture) => gesture.tools),
                    this.model.actions,
                    this.model.inspectors,
                    this.model.auxiliaries,
                ];
                const buttons = groups.map((group) => {
                    return group
                        .filter((tool) => tool.visible)
                        .map((tool) => tool.tool_button());
                });
                return buttons;
            }
            else {
                return split(buttons, null);
            }
        })();
        await build_views(this._tool_button_views, this._tool_buttons.flat(), { parent: this });
    }
    set_visibility(visible) {
        if (visible != this._visible) {
            this._visible = visible;
            this._on_visible_change();
        }
    }
    _on_visible_change() {
        this.el.classList.toggle(toolbars.hidden, !this.visible);
    }
    _after_resize() {
        super._after_resize();
        this._after_render();
    }
    _menu_at() {
        switch (this.model.location) {
            case "right": return { left_of: this._overflow_el };
            case "left": return { right_of: this._overflow_el };
            case "above": return { below: this._overflow_el };
            case "below": return { above: this._overflow_el };
        }
    }
    toggle_menu() {
        this._overflow_menu.toggle(this._menu_at());
    }
    render() {
        super.render();
        this.el.classList.add(toolbars[this.model.location]);
        this.el.classList.toggle(toolbars.inner, this.model.inner);
        this._on_visible_change();
        const { horizontal } = this.model;
        this._overflow_el = div({ class: toolbars.tool_overflow, tabIndex: 0 }, horizontal ? "⋮" : "⋯");
        this._overflow_el.addEventListener("click", (_event) => {
            this.toggle_menu();
        });
        this._overflow_el.addEventListener("keydown", (event) => {
            if (event.key == "Enter") {
                this.toggle_menu();
            }
        });
        this._items = [];
        if (this.model.logo != null) {
            const gray = this.model.logo === "grey" ? logos.grey : null;
            const logo_el = a({ href: "https://bokeh.org/", target: "_blank", class: [logos.logo, logos.logo_small, gray] });
            this._items.push(logo_el);
            this.shadow_el.appendChild(logo_el);
        }
        for (const [, button_view] of this._tool_button_views) {
            button_view.render_to(this.shadow_el);
        }
        const bars = this._tool_buttons.map((group) => group.map((button) => this._tool_button_views.get(button).el));
        const non_empty = bars.filter((bar) => bar.length != 0);
        const divider = () => div({ class: toolbars.divider });
        for (const el of join(non_empty, divider)) {
            this._items.push(el);
            this.shadow_el.append(el);
        }
    }
    _after_render() {
        super._after_render();
        clear(this._overflow_menu.items);
        if (this.shadow_el.contains(this._overflow_el)) {
            this.shadow_el.removeChild(this._overflow_el);
        }
        for (const el of this._items) {
            if (!this.shadow_el.contains(el)) {
                this.shadow_el.append(el);
            }
        }
        const { horizontal } = this.model;
        const overflow_size = 15;
        const { bbox } = this;
        const overflow_cls = horizontal ? toolbars.right : toolbars.above;
        let size = 0;
        let overflowed = false;
        for (const el of this._items) {
            if (overflowed) {
                this.shadow_el.removeChild(el);
                this._overflow_menu.items.push({ custom: el, class: overflow_cls });
            }
            else {
                const { width, height } = el.getBoundingClientRect();
                size += horizontal ? width : height;
                overflowed = horizontal ? size > bbox.width - overflow_size : size > bbox.height - overflow_size;
                if (overflowed) {
                    this.shadow_el.removeChild(el);
                    this.shadow_el.appendChild(this._overflow_el);
                    this._overflow_menu.items.push({ custom: el, class: overflow_cls });
                }
            }
        }
        if (this._overflow_menu.is_open) {
            this._overflow_menu.show(this._menu_at());
        }
    }
}
import { Struct, Ref, Nullable, List, Or } from "../../core/kinds";
const GestureToolLike = Or(Ref(GestureTool), Ref((ToolProxy)));
const GestureEntry = Struct({
    tools: List(GestureToolLike),
    active: Nullable(GestureToolLike),
});
const GesturesMap = Struct({
    pan: GestureEntry,
    scroll: GestureEntry,
    pinch: GestureEntry,
    rotate: GestureEntry,
    move: GestureEntry,
    tap: GestureEntry,
    doubletap: GestureEntry,
    press: GestureEntry,
    pressup: GestureEntry,
    multi: GestureEntry,
});
export const Inspection = Tool;
function create_gesture_map() {
    return {
        pan: { tools: [], active: null },
        scroll: { tools: [], active: null },
        pinch: { tools: [], active: null },
        rotate: { tools: [], active: null },
        move: { tools: [], active: null },
        tap: { tools: [], active: null },
        doubletap: { tools: [], active: null },
        press: { tools: [], active: null },
        pressup: { tools: [], active: null },
        multi: { tools: [], active: null },
    };
}
export class Toolbar extends UIElement {
    static __name__ = "Toolbar";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.prototype.default_view = ToolbarView;
        this.define(({ Bool, List, Or, Ref, Nullable, Auto }) => ({
            tools: [List(Or(Ref(Tool), Ref(ToolProxy))), []],
            logo: [Nullable(Logo), "normal"],
            autohide: [Bool, false],
            active_drag: [Nullable(Or(GestureToolLike, Auto)), "auto"],
            active_inspect: [Nullable(Or(Ref(Inspection), List(Ref(Inspection)), Ref(ToolProxy), Auto)), "auto"],
            active_scroll: [Nullable(Or(GestureToolLike, Auto)), "auto"],
            active_tap: [Nullable(Or(GestureToolLike, Auto)), "auto"],
            active_multi: [Nullable(Or(GestureToolLike, Auto)), "auto"],
        }));
        this.internal(({ List, Bool, Ref, Or, Null, Auto }) => {
            return {
                buttons: [Or(List(Or(Ref(ToolButton), Null)), Auto), "auto"],
                location: [Location, "right"],
                inner: [Bool, false],
                gestures: [GesturesMap, create_gesture_map],
                actions: [List(Or(Ref(ActionTool), Ref(ToolProxy))), []],
                inspectors: [List(Or(Ref(InspectTool), Ref(ToolProxy))), []],
                auxiliaries: [List(Or(Ref(Tool), Ref(ToolProxy))), []],
                help: [List(Or(Ref(HelpTool), Ref(ToolProxy))), []],
            };
        });
    }
    get horizontal() {
        return this.location == "above" || this.location == "below";
    }
    get vertical() {
        return this.location == "left" || this.location == "right";
    }
    connect_signals() {
        super.connect_signals();
        const { tools, active_drag, active_inspect, active_scroll, active_tap, active_multi } = this.properties;
        this.on_change([tools, active_drag, active_inspect, active_scroll, active_tap, active_multi], () => {
            this._init_tools();
            this._activate_tools();
        });
    }
    initialize() {
        super.initialize();
        this._init_tools();
        this._activate_tools();
    }
    _init_tools() {
        const visited = new Set();
        function isa(tool, type) {
            const is = (tool instanceof ToolProxy ? tool.underlying : tool) instanceof type;
            if (is) {
                visited.add(tool);
            }
            return is;
        }
        const new_inspectors = this.tools.filter(t => isa(t, InspectTool));
        this.inspectors = new_inspectors;
        const new_help = this.tools.filter(t => isa(t, HelpTool));
        this.help = new_help;
        const new_actions = this.tools.filter(t => isa(t, ActionTool));
        this.actions = new_actions;
        const new_gestures = create_gesture_map();
        for (const tool of this.tools) {
            if (isa(tool, GestureTool)) {
                new_gestures[tool.event_role].tools.push(tool);
            }
        }
        for (const et of typed_keys(new_gestures)) {
            const gesture = this.gestures[et];
            gesture.tools = sort_by(new_gestures[et].tools, (tool) => tool.default_order);
            if (gesture.active != null && every(gesture.tools, (tool) => tool.id != gesture.active?.id)) {
                gesture.active = null;
            }
        }
        const new_auxiliaries = this.tools.filter((tool) => !visited.has(tool));
        this.auxiliaries = new_auxiliaries;
    }
    _activate_tools() {
        if (this.active_inspect == "auto") {
            // do nothing as all tools are active be default
        }
        else if (this.active_inspect == null) {
            for (const inspector of this.inspectors) {
                inspector.active = false;
            }
        }
        else if (isArray(this.active_inspect)) {
            const active_inspect = intersection(this.active_inspect, this.inspectors);
            if (active_inspect.length != this.active_inspect.length) {
                this.active_inspect = active_inspect;
            }
            for (const inspector of this.inspectors) {
                if (!includes(this.active_inspect, inspector)) {
                    inspector.active = false;
                }
            }
        }
        else {
            let found = false;
            for (const inspector of this.inspectors) {
                if (inspector != this.active_inspect) {
                    inspector.active = false;
                }
                else {
                    found = true;
                }
            }
            if (!found) {
                this.active_inspect = null;
            }
        }
        const _activate_gesture = (tool) => {
            if (tool.active) {
                // tool was activated by a proxy, but we need to finish configuration manually
                this._active_change(tool);
            }
            else {
                tool.active = true;
            }
        };
        // Connecting signals has to be done before changing the active state of the tools.
        for (const gesture of values(this.gestures)) {
            for (const tool of gesture.tools) {
                // XXX: connect once
                this.connect(tool.properties.active.change, () => this._active_change(tool));
            }
        }
        function _get_active_attr(et) {
            switch (et) {
                case "tap": return "active_tap";
                case "pan": return "active_drag";
                case "pinch":
                case "scroll": return "active_scroll";
                case "multi": return "active_multi";
                default: return null;
            }
        }
        function _supports_auto(et, tool) {
            return et == "tap" || et == "pan" || tool.supports_auto();
        }
        const is_active_gesture = (active_tool) => {
            return this.tools.includes(active_tool) || (active_tool instanceof Tool && this.tools.some((tool) => tool instanceof ToolProxy && tool.tools.includes(active_tool)));
        };
        for (const [event_role, gesture] of entries(this.gestures)) {
            const et = event_role;
            const active_attr = _get_active_attr(et);
            if (active_attr != null) {
                const active_tool = this[active_attr];
                if (active_tool == "auto") {
                    if (gesture.tools.length != 0) {
                        const [tool] = gesture.tools;
                        if (_supports_auto(et, tool)) {
                            _activate_gesture(tool);
                        }
                    }
                }
                else if (active_tool != null) {
                    if (is_active_gesture(active_tool)) {
                        _activate_gesture(active_tool);
                    }
                    else {
                        this[active_attr] = null;
                    }
                }
                else {
                    this.gestures[et].active = null;
                    for (const tool of this.gestures[et].tools) {
                        tool.active = false;
                    }
                }
            }
        }
    }
    _active_change(tool) {
        const { event_types } = tool;
        for (const et of event_types) {
            if (tool.active) {
                const currently_active_tool = this.gestures[et].active;
                if (currently_active_tool != null && tool != currently_active_tool) {
                    logger.debug(`Toolbar: deactivating tool: ${currently_active_tool} for event type '${et}'`);
                    currently_active_tool.active = false;
                }
                this.gestures[et].active = tool;
                logger.debug(`Toolbar: activating tool: ${tool} for event type '${et}'`);
            }
            else {
                this.gestures[et].active = null;
            }
        }
    }
}
//# sourceMappingURL=toolbar.js.map