import { CartesianFrame } from "../canvas/cartesian_frame";
import { Canvas } from "../canvas/canvas";
import { RendererView } from "../renderers/renderer";
import { CompositeRendererView } from "../renderers/composite_renderer";
import { ToolProxy } from "../tools/tool_proxy";
import { LayoutDOMView } from "../layouts/layout_dom";
import { Annotation, AnnotationView } from "../annotations/annotation";
import { Title } from "../annotations/title";
import { AxisView } from "../axes/axis";
import { ToolbarPanel } from "../annotations/toolbar_panel";
import { is_auto_ranged } from "../ranges/data_range1d";
import { Panel } from "../ui/panel";
import { Div } from "../dom/elements";
import { Reset } from "../../core/bokeh_events";
import { build_views, remove_views } from "../../core/build_views";
import { Visuals } from "../../core/visuals";
import { logger } from "../../core/logging";
import { RangesUpdate } from "../../core/bokeh_events";
import { Signal0 } from "../../core/signaling";
import { throttle } from "../../core/util/throttle";
import { isBoolean, isArray, isString } from "../../core/util/types";
import { copy, reversed } from "../../core/util/array";
import { flat_map } from "../../core/util/iterator";
import { CanvasLayer } from "../../core/util/canvas";
import { HStack, VStack, NodeLayout } from "../../core/layout/alignments";
import { BorderLayout } from "../../core/layout/border";
import { Row, Column } from "../../core/layout/grid";
import { SidePanel } from "../../core/layout/side_panel";
import { BBox } from "../../core/util/bbox";
import { parse_css_font_size } from "../../core/util/text";
import { RangeManager } from "./range_manager";
import { StateManager } from "./state_manager";
import { settings } from "../../core/settings";
import { InlineStyleSheet, px } from "../../core/dom";
import { Node } from "../coordinates/node";
import plots_css from "../../styles/plots.css";
import attribution_css from "../../styles/attribution.css";
const { max } = Math;
export class PlotView extends LayoutDOMView {
    static __name__ = "PlotView";
    visuals;
    _frame;
    frame_view;
    get frame() {
        return this.frame_view;
    }
    _canvas;
    canvas_view;
    get canvas() {
        return this.canvas_view;
    }
    _render_count = 0;
    repainted = new Signal0(this, "repainted");
    _computed_style = new InlineStyleSheet();
    stylesheets() {
        return [...super.stylesheets(), plots_css, this._computed_style];
    }
    _title;
    _toolbar;
    _attribution;
    _notifications;
    get toolbar_panel() {
        return this._toolbar != null ? this.views.find_one(this._toolbar) : null;
    }
    _outer_bbox = new BBox();
    _inner_bbox = new BBox();
    _needs_paint = true;
    _invalidated_painters = new Set();
    _invalidate_all = true;
    _state_manager;
    _range_manager;
    get state() {
        return this._state_manager;
    }
    set invalidate_dataranges(value) {
        this._range_manager.invalidate_dataranges = value;
    }
    lod_started;
    _initial_state;
    throttled_paint;
    computed_renderers = [];
    get computed_renderer_views() {
        return this
            .computed_renderers
            .map((r) => this.renderer_views.get(r))
            .filter((rv) => rv != null); // TODO race condition again
    }
    get all_renderer_views() {
        const collected = [];
        for (const rv of this.computed_renderer_views) {
            collected.push(rv);
            if (rv instanceof CompositeRendererView) {
                collected.push(...rv.computed_renderer_views);
            }
        }
        return collected;
    }
    get auto_ranged_renderers() {
        return this.computed_renderer_views.filter(is_auto_ranged);
    }
    get base_font_size() {
        const font_size = getComputedStyle(this.el).fontSize;
        const result = parse_css_font_size(font_size);
        if (result != null) {
            const { value, unit } = result;
            if (unit == "px") {
                return value;
            }
        }
        return null;
    }
    /*protected*/ renderer_views = new Map();
    /*protected*/ tool_views = new Map();
    *children() {
        yield* super.children();
        yield* this.renderer_views.values();
        yield* this.tool_views.values();
    }
    get child_models() {
        return [];
    }
    _is_paused = 0;
    get is_paused() {
        return this._is_paused != 0;
    }
    pause() {
        this._is_paused += 1;
    }
    unpause(no_render = false) {
        this._is_paused = max(this._is_paused - 1, 0);
        if (!this.is_paused && !no_render) {
            this.request_repaint();
        }
    }
    _needs_notify = false;
    notify_finished_after_paint() {
        this._needs_notify = true;
    }
    request_repaint() {
        this.request_paint();
    }
    request_paint(...to_invalidate) {
        this.invalidate_painters(...to_invalidate);
        this.schedule_paint();
    }
    invalidate_painters(...to_invalidate) {
        if (to_invalidate.length == 0) {
            this._invalidate_all = true;
            return;
        }
        for (const item of to_invalidate) {
            const view = (() => {
                if (item instanceof RendererView) {
                    return item;
                }
                else {
                    return this.views.get_one(item);
                }
            })();
            this._invalidated_painters.add(view);
        }
    }
    schedule_paint() {
        if (!this.is_paused) {
            const promise = this.throttled_paint();
            this._ready = this._ready.then(() => promise);
        }
    }
    request_layout() {
        this.request_repaint();
    }
    reset() {
        if (this.model.reset_policy == "standard") {
            this.state.clear();
            this.reset_range();
            this.reset_selection();
        }
        this.model.trigger_event(new Reset());
    }
    remove() {
        remove_views(this.renderer_views);
        remove_views(this.tool_views);
        super.remove();
    }
    get_context_menu(xy) {
        const { x, y } = xy;
        for (const rv of reversed([...this.renderer_views.values()])) {
            if (rv.context_menu != null && rv.interactive_hit?.(x, y) == true) {
                return rv.context_menu;
            }
        }
        return super.get_context_menu(xy);
    }
    initialize() {
        this.pause();
        super.initialize();
        this.lod_started = false;
        this.visuals = new Visuals(this);
        this._initial_state = {
            selection: new Map(), // XXX: initial selection?
        };
        this._frame = new CartesianFrame({
            x_scale: this.model.x_scale,
            y_scale: this.model.y_scale,
            x_range: this.model.x_range,
            y_range: this.model.y_range,
            extra_x_ranges: this.model.extra_x_ranges,
            extra_y_ranges: this.model.extra_y_ranges,
            extra_x_scales: this.model.extra_x_scales,
            extra_y_scales: this.model.extra_y_scales,
            aspect_scale: this.model.aspect_scale,
            match_aspect: this.model.match_aspect,
        });
        this._range_manager = new RangeManager(this);
        this._state_manager = new StateManager(this, this._initial_state);
        this.throttled_paint = throttle(() => {
            if (!this.is_destroyed) {
                this.repaint();
            }
        }, 1000 / 60);
        const { title_location, title } = this.model;
        if (title_location != null && title != null) {
            this._title = title instanceof Title ? title : new Title({ text: title });
        }
        const { toolbar_location, toolbar_inner, toolbar } = this.model;
        if (toolbar_location != null) {
            this._toolbar = new ToolbarPanel({ toolbar });
            toolbar.location = toolbar_location;
            toolbar.inner = toolbar_inner;
        }
        const { hidpi, output_backend } = this.model;
        this._canvas = new Canvas({ hidpi, output_backend });
        this._attribution = new Panel({
            position: new Node({ target: "frame", symbol: "bottom_right" }),
            anchor: "bottom_right",
            elements: [],
            css_variables: {
                "--max-width": new Node({ target: "frame", symbol: "width" }),
            },
            stylesheets: [attribution_css],
        });
        this._notifications = new Panel({
            position: new Node({ target: this.model, symbol: "top_center" }),
            anchor: "top_center",
            elements: [],
            stylesheets: [`
        :host {
          display: flex;
          flex-direction: column;
          gap: 1em;
          width: max-content;
          max-width: 80%;
        }

        :host:empty {
          display: none;
        }

        :host > div {
          padding: 0.5em;
          border: 1px solid gray;
          border-radius: 0.5em;
          opacity: 0.8;
        }
      `],
        });
    }
    get elements() {
        return [this._canvas, this._frame, this._attribution, this._notifications, ...super.elements];
    }
    async lazy_initialize() {
        await super.lazy_initialize();
        this.canvas_view = this._element_views.get(this._canvas);
        this.canvas_view.plot_views = [this];
        this.frame_view = this._element_views.get(this._frame);
        await this.build_tool_views();
        await this.build_renderer_views();
        this._range_manager.update_dataranges();
    }
    box_sizing() {
        const { width_policy, height_policy, ...sizing } = super.box_sizing();
        const { frame_width, frame_height } = this.model;
        return {
            ...sizing,
            width_policy: frame_width != null && width_policy == "auto" ? "fit" : width_policy,
            height_policy: frame_height != null && height_policy == "auto" ? "fit" : height_policy,
        };
    }
    _intrinsic_display() {
        return { inner: this.model.flow_mode, outer: "grid" };
    }
    _update_layout() {
        super._update_layout();
        // TODO: invalidating all should imply "needs paint"
        this._invalidate_all = true;
        this._needs_paint = true;
        const layout = new BorderLayout();
        const { frame_align } = this.model;
        layout.aligns = (() => {
            if (isBoolean(frame_align)) {
                return { left: frame_align, right: frame_align, top: frame_align, bottom: frame_align };
            }
            else {
                const { left = true, right = true, top = true, bottom = true } = frame_align;
                return { left, right, top, bottom };
            }
        })();
        layout.set_sizing({ width_policy: "max", height_policy: "max" });
        if (this.visuals.outline_line.doit) {
            const width = this.visuals.outline_line.line_width.get_value();
            layout.center_border_width = width;
        }
        const outer_above = copy(this.model.above);
        const outer_below = copy(this.model.below);
        const outer_left = copy(this.model.left);
        const outer_right = copy(this.model.right);
        const inner_above = [];
        const inner_below = [];
        const inner_left = [];
        const inner_right = [];
        const get_side = (side, inner = false) => {
            switch (side) {
                case "above": return inner ? inner_above : outer_above;
                case "below": return inner ? inner_below : outer_below;
                case "left": return inner ? inner_left : outer_left;
                case "right": return inner ? inner_right : outer_right;
            }
        };
        const { title_location } = this.model;
        if (title_location != null && this._title != null) {
            get_side(title_location).push(this._title);
        }
        if (this._toolbar != null) {
            const { location } = this._toolbar.toolbar;
            if (!this.model.toolbar_inner) {
                const panels = get_side(location);
                let push_toolbar = true;
                if (this.model.toolbar_sticky) {
                    for (let i = 0; i < panels.length; i++) {
                        const panel = panels[i];
                        if (panel instanceof Title) {
                            if (location == "above" || location == "below") {
                                panels[i] = [panel, this._toolbar];
                            }
                            else {
                                panels[i] = [this._toolbar, panel];
                            }
                            push_toolbar = false;
                            break;
                        }
                    }
                }
                if (push_toolbar) {
                    panels.push(this._toolbar);
                }
            }
            else {
                const panels = get_side(location, true);
                panels.push(this._toolbar);
            }
        }
        const set_layout = (side, model) => {
            const view = this.views.get_one(model);
            view.panel = new SidePanel(side);
            view.update_layout?.();
            return view.layout;
        };
        const set_layouts = (side, panels) => {
            const horizontal = side == "above" || side == "below";
            const layouts = [];
            for (const panel of panels) {
                if (isArray(panel)) {
                    const items = panel.map((subpanel) => {
                        const item = set_layout(side, subpanel);
                        if (item == null) {
                            return undefined;
                        }
                        if (subpanel instanceof ToolbarPanel) {
                            const dim = horizontal ? "width_policy" : "height_policy";
                            item.set_sizing({ ...item.sizing, [dim]: "min" });
                        }
                        return item;
                    }).filter((item) => item != null);
                    let layout;
                    if (horizontal) {
                        layout = new Row(items);
                        layout.set_sizing({ width_policy: "max", height_policy: "min" });
                    }
                    else {
                        layout = new Column(items);
                        layout.set_sizing({ width_policy: "min", height_policy: "max" });
                    }
                    layout.absolute = true;
                    layouts.push(layout);
                }
                else {
                    const layout = set_layout(side, panel);
                    if (layout != null) {
                        layouts.push(layout);
                    }
                }
            }
            return layouts;
        };
        const min_border = this.model.min_border ?? 0;
        layout.min_border = {
            left: this.model.min_border_left ?? min_border,
            top: this.model.min_border_top ?? min_border,
            right: this.model.min_border_right ?? min_border,
            bottom: this.model.min_border_bottom ?? min_border,
        };
        const center_panel = new NodeLayout();
        const top_panel = new VStack();
        const bottom_panel = new VStack();
        const left_panel = new HStack();
        const right_panel = new HStack();
        const inner_top_panel = new VStack();
        const inner_bottom_panel = new VStack();
        const inner_left_panel = new HStack();
        const inner_right_panel = new HStack();
        center_panel.absolute = true;
        top_panel.absolute = true;
        bottom_panel.absolute = true;
        left_panel.absolute = true;
        right_panel.absolute = true;
        inner_top_panel.absolute = true;
        inner_bottom_panel.absolute = true;
        inner_left_panel.absolute = true;
        inner_right_panel.absolute = true;
        center_panel.children =
            this.model.center.filter((obj) => {
                return obj instanceof Annotation;
            }).map((model) => {
                const view = this.views.get_one(model);
                view.update_layout?.();
                return view.layout;
            }).filter((layout) => layout != null);
        const { frame_width, frame_height } = this.model;
        center_panel.set_sizing({
            ...(frame_width != null ? { width_policy: "fixed", width: frame_width } : { width_policy: "fit" }),
            ...(frame_height != null ? { height_policy: "fixed", height: frame_height } : { height_policy: "fit" }),
        });
        center_panel.on_resize((bbox) => this.frame.set_geometry(bbox));
        top_panel.children = reversed(set_layouts("above", outer_above));
        bottom_panel.children = set_layouts("below", outer_below);
        left_panel.children = reversed(set_layouts("left", outer_left));
        right_panel.children = set_layouts("right", outer_right);
        inner_top_panel.children = set_layouts("above", inner_above);
        inner_bottom_panel.children = set_layouts("below", inner_below);
        inner_left_panel.children = set_layouts("left", inner_left);
        inner_right_panel.children = set_layouts("right", inner_right);
        top_panel.set_sizing({ width_policy: "fit", height_policy: "min" /*, min_height: layout.min_border.top*/ });
        bottom_panel.set_sizing({ width_policy: "fit", height_policy: "min" /*, min_height: layout.min_width.bottom*/ });
        left_panel.set_sizing({ width_policy: "min", height_policy: "fit" /*, min_width: layout.min_width.left*/ });
        right_panel.set_sizing({ width_policy: "min", height_policy: "fit" /*, min_width: layout.min_width.right*/ });
        inner_top_panel.set_sizing({ width_policy: "fit", height_policy: "min" });
        inner_bottom_panel.set_sizing({ width_policy: "fit", height_policy: "min" });
        inner_left_panel.set_sizing({ width_policy: "min", height_policy: "fit" });
        inner_right_panel.set_sizing({ width_policy: "min", height_policy: "fit" });
        layout.center_panel = center_panel;
        layout.top_panel = top_panel;
        layout.bottom_panel = bottom_panel;
        layout.left_panel = left_panel;
        layout.right_panel = right_panel;
        if (inner_top_panel.children.length != 0) {
            layout.inner_top_panel = inner_top_panel;
        }
        if (inner_bottom_panel.children.length != 0) {
            layout.inner_bottom_panel = inner_bottom_panel;
        }
        if (inner_left_panel.children.length != 0) {
            layout.inner_left_panel = inner_left_panel;
        }
        if (inner_right_panel.children.length != 0) {
            layout.inner_right_panel = inner_right_panel;
        }
        this.layout = layout;
    }
    _measure_layout() {
        const { frame_width, frame_height } = this.model;
        const frame = {
            width: frame_width == null ? "1fr" : px(frame_width),
            height: frame_height == null ? "1fr" : px(frame_height),
        };
        const { layout } = this;
        const top = layout.top_panel.measure({ width: Infinity, height: Infinity });
        const bottom = layout.bottom_panel.measure({ width: Infinity, height: Infinity });
        const left = layout.left_panel.measure({ width: Infinity, height: Infinity });
        const right = layout.right_panel.measure({ width: Infinity, height: Infinity });
        const top_height = max(top.height, layout.min_border.top);
        const bottom_height = max(bottom.height, layout.min_border.bottom);
        const left_width = max(left.width, layout.min_border.left);
        const right_width = max(right.width, layout.min_border.right);
        this._computed_style.replace(`
      :host {
        grid-template-rows: ${top_height}px ${frame.height} ${bottom_height}px;
        grid-template-columns: ${left_width}px ${frame.width} ${right_width}px;
      }
    `);
    }
    get axis_views() {
        const views = [];
        for (const [, renderer_view] of this.renderer_views) {
            if (renderer_view instanceof AxisView) {
                views.push(renderer_view);
            }
        }
        return views;
    }
    update_range(range_info, options) {
        this.pause();
        this._range_manager.update(range_info, options);
        this.unpause();
    }
    reset_range() {
        this.pause();
        this._range_manager.reset();
        this.unpause();
        this.trigger_ranges_update_event();
    }
    trigger_ranges_update_event(extra_ranges = []) {
        /**
         * Emits `RangesUpdate` event on all plots linked by all
         * ranges managed by this plot's range manager and linked
         * by additional context dependent ranges (`extra_ranges`).
         */
        const { x_ranges, y_ranges } = this._range_manager.ranges();
        const ranges = [...x_ranges, ...y_ranges, ...extra_ranges];
        const linked_plots = new Set(ranges.flatMap((r) => [...r.linked_plots]));
        for (const plot_view of linked_plots) {
            const { x_range, y_range } = plot_view.model;
            const event = new RangesUpdate(x_range.start, x_range.end, y_range.start, y_range.end);
            plot_view.model.trigger_event(event);
        }
    }
    get_selection() {
        const selection = new Map();
        for (const renderer of this.model.data_renderers) {
            const { selected } = renderer.selection_manager.source;
            selection.set(renderer, selected);
        }
        return selection;
    }
    update_selection(selections) {
        for (const renderer of this.model.data_renderers) {
            const ds = renderer.selection_manager.source;
            if (selections != null) {
                const selection = selections.get(renderer);
                if (selection != null) {
                    ds.selected.update(selection, true);
                }
            }
            else {
                ds.selection_manager.clear();
            }
        }
    }
    reset_selection() {
        this.update_selection(null);
    }
    _invalidate_layout_if_needed() {
        const needs_layout = (() => {
            for (const panel of this.model.side_panels) {
                const view = this.renderer_views.get(panel);
                if (view.layout?.has_size_changed() ?? false) {
                    this.invalidate_painters(view);
                    return true;
                }
            }
            return false;
        })();
        if (needs_layout) {
            this.compute_layout();
        }
    }
    *_compute_renderers() {
        const { above, below, left, right, center, renderers } = this.model;
        yield* renderers;
        yield* above;
        yield* below;
        yield* left;
        yield* right;
        yield* center;
        if (this._title != null) {
            yield this._title;
        }
        if (this._toolbar != null) {
            yield this._toolbar;
        }
        for (const [, view] of this.tool_views) {
            yield* view.overlays;
        }
    }
    _update_attribution() {
        const attribution = [
            ...this.model.attribution,
            ...this.computed_renderer_views.map((rv) => rv.attribution),
        ].filter((rv) => rv != null);
        const elements = attribution.map((attrib) => isString(attrib) ? new Div({ children: [attrib] }) : attrib);
        this._attribution.elements = elements;
        // TODO this._attribution.title = contents_el.textContent!.replace(/\s*\n\s*/g, " ")
    }
    async _build_renderers() {
        this.computed_renderers = [...this._compute_renderers()];
        const result = await build_views(this.renderer_views, this.computed_renderers, { parent: this });
        this._update_attribution();
        return result;
    }
    async _update_renderers() {
        const { created } = await this._build_renderers();
        const created_renderers = new Set(created);
        // First remove and then either reattach existing renderers or render and
        // attach new renderers, so that the order of children is consistent, while
        // avoiding expensive re-rendering of existing views.
        for (const renderer_view of this.renderer_views.values()) {
            renderer_view.el.remove();
        }
        for (const renderer_view of this.renderer_views.values()) {
            const is_new = created_renderers.has(renderer_view);
            const target = renderer_view.rendering_target();
            if (is_new) {
                renderer_view.render_to(target);
            }
            else {
                target.append(renderer_view.el);
            }
        }
        this.r_after_render();
    }
    async build_renderer_views() {
        await this._build_renderers();
    }
    async build_tool_views() {
        const tool_models = flat_map(this.model.toolbar.tools, (item) => item instanceof ToolProxy ? item.tools : [item]);
        const { created } = await build_views(this.tool_views, [...tool_models], { parent: this });
        created.map((tool_view) => this.canvas_view.ui_event_bus.register_tool(tool_view));
    }
    connect_signals() {
        super.connect_signals();
        const { x_range, y_range, x_scale, y_scale, extra_x_ranges, extra_y_ranges, extra_x_scales, extra_y_scales, aspect_scale, match_aspect, } = this.model.properties;
        this.on_change([
            x_range, y_range,
            x_scale, y_scale,
            extra_x_ranges, extra_y_ranges,
            extra_x_scales, extra_y_scales,
            aspect_scale, match_aspect,
        ], () => {
            const { x_range, y_range, x_scale, y_scale, extra_x_ranges, extra_y_ranges, extra_x_scales, extra_y_scales, aspect_scale, match_aspect, } = this.model;
            this._frame.setv({
                x_range, y_range,
                x_scale, y_scale,
                extra_x_ranges, extra_y_ranges,
                extra_x_scales, extra_y_scales,
                aspect_scale, match_aspect,
            });
        });
        const { above, below, left, right, center, renderers } = this.model.properties;
        const panels = [above, below, left, right, center];
        this.on_change(renderers, async () => {
            await this._update_renderers();
        });
        this.on_change(panels, async () => {
            await this._update_renderers();
            this.invalidate_layout();
        });
        this.connect(this.model.toolbar.properties.tools.change, async () => {
            await this.build_tool_views();
            await this._update_renderers();
        });
        const { x_ranges, y_ranges } = this.frame;
        for (const [, range] of x_ranges) {
            this.connect(range.change, () => {
                this.request_repaint();
            });
        }
        for (const [, range] of y_ranges) {
            this.connect(range.change, () => {
                this.request_repaint();
            });
        }
        this.connect(this.model.change, () => this.request_repaint());
        this.connect(this.model.reset, () => this.reset());
        const { toolbar_location } = this.model.properties;
        this.on_change(toolbar_location, async () => {
            const { toolbar_location } = this.model;
            if (this._toolbar != null) {
                if (toolbar_location != null) {
                    this._toolbar.toolbar.location = toolbar_location;
                }
                else {
                    this._toolbar = undefined;
                    await this._update_renderers();
                }
            }
            else {
                if (toolbar_location != null) {
                    const { toolbar, toolbar_inner } = this.model;
                    this._toolbar = new ToolbarPanel({ toolbar });
                    toolbar.location = toolbar_location;
                    toolbar.inner = toolbar_inner;
                    await this._update_renderers();
                }
            }
            this.invalidate_layout();
        });
        const { hold_render } = this.model.properties;
        this.on_change(hold_render, () => {
            if (!this.model.hold_render) {
                this.request_repaint();
            }
        });
    }
    has_finished() {
        if (!super.has_finished()) {
            return false;
        }
        if (this.model.visible) {
            for (const [, renderer_view] of this.renderer_views) {
                if (!renderer_view.has_finished()) {
                    return false;
                }
            }
        }
        return true;
    }
    _after_layout() {
        super._after_layout();
        this.unpause(true);
        const left = this.layout.left_panel.bbox;
        const right = this.layout.right_panel.bbox;
        const center = this.layout.center_panel.bbox;
        const top = this.layout.top_panel.bbox;
        const bottom = this.layout.bottom_panel.bbox;
        const { bbox } = this;
        const top_height = top.bottom;
        const bottom_height = bbox.height - bottom.top;
        const left_width = left.right;
        const right_width = bbox.width - right.left;
        // TODO: don't replace here; inject stylesheet?
        this.canvas.style.replace(`
      .bk-layer.bk-events {
        display: grid;
        grid-template-areas:
          ".    above  .    "
          "left center right"
          ".    below  .    ";
        grid-template-rows: ${px(top_height)} ${px(center.height)} ${px(bottom_height)};
        grid-template-columns: ${px(left_width)} ${px(center.width)} ${px(right_width)};
      }
    `);
        for (const [, child_view] of this.renderer_views) {
            if (child_view instanceof AnnotationView) {
                child_view.after_layout?.();
            }
        }
        this.model.setv({
            inner_width: Math.round(this.frame.bbox.width),
            inner_height: Math.round(this.frame.bbox.height),
            outer_width: Math.round(this.bbox.width),
            outer_height: Math.round(this.bbox.height),
        }, { no_change: true });
        if (this.model.match_aspect) {
            this.pause();
            this._range_manager.update_dataranges();
            this.unpause(true);
        }
        if (!this._outer_bbox.equals(this.bbox)) {
            this.canvas_view.resize(); // XXX temporary hack
            this._outer_bbox = this.bbox;
            this._invalidate_all = true;
            this._needs_paint = true;
        }
        const { inner_bbox } = this.layout;
        if (!this._inner_bbox.equals(inner_bbox)) {
            this._inner_bbox = inner_bbox;
            this._invalidate_all = true;
            this._needs_paint = true;
        }
        if (this._needs_paint) {
            // XXX: can't be this.request_paint(), because it would trigger back-and-forth
            // layout recomputing feedback loop between plots. Plots are also much more
            // responsive this way, especially in interactive mode.
            this.paint();
        }
    }
    render() {
        super.render();
        for (const renderer_view of this.computed_renderer_views) {
            const target = renderer_view.rendering_target();
            renderer_view.render_to(target);
        }
    }
    repaint() {
        this._invalidate_layout_if_needed();
        this.paint();
    }
    paint() {
        if (this.is_paused || this.model.hold_render) {
            return;
        }
        if (this.is_displayed) {
            logger.trace(`${this.toString()}.paint()`);
            this._actual_paint();
        }
        else {
            // This is possibly the first render cycle, but plot isn't displayed,
            // so all renderers have to be manually marked as finished, because
            // their `render()` method didn't run.
            for (const renderer_view of this.computed_renderer_views) {
                renderer_view.force_finished();
            }
        }
        if (this._needs_notify) {
            this._needs_notify = false;
            this.notify_finished();
        }
    }
    _actual_paint() {
        logger.trace(`${this.toString()}._actual_paint ${this._render_count} start`);
        const { document } = this.model;
        if (document != null) {
            const interactive_duration = document.interactive_duration();
            if (interactive_duration >= 0 && interactive_duration < this.model.lod_interval) {
                setTimeout(() => {
                    if (document.interactive_duration() > this.model.lod_timeout) {
                        document.interactive_stop();
                    }
                    this.request_repaint(); // TODO: this.schedule_paint()
                }, this.model.lod_timeout);
            }
            else {
                document.interactive_stop();
            }
        }
        if (this._range_manager.invalidate_dataranges) {
            this._range_manager.update_dataranges();
            this._invalidate_layout_if_needed();
        }
        let do_primary = false;
        let do_overlays = false;
        if (this._invalidate_all) {
            do_primary = true;
            do_overlays = true;
        }
        else {
            for (const painter of this._invalidated_painters) {
                const { level } = painter.model;
                if (level != "overlay") {
                    do_primary = true;
                }
                else {
                    do_overlays = true;
                }
                if (do_primary && do_overlays) {
                    break;
                }
            }
        }
        this._invalidated_painters.clear();
        this._invalidate_all = false;
        const frame_box = [
            this.frame.bbox.left,
            this.frame.bbox.top,
            this.frame.bbox.width,
            this.frame.bbox.height,
        ];
        const { primary, overlays } = this.canvas_view;
        if (do_primary) {
            primary.prepare();
            this.canvas_view.prepare_webgl(frame_box);
            this._paint_empty(primary.ctx, frame_box);
            this._paint_outline(primary.ctx, frame_box);
            this._paint_levels(primary.ctx, "image", frame_box, true);
            this._paint_levels(primary.ctx, "underlay", frame_box, true);
            this._paint_levels(primary.ctx, "glyph", frame_box, true);
            this._paint_levels(primary.ctx, "guide", frame_box, false);
            this._paint_levels(primary.ctx, "annotation", frame_box, false);
            primary.finish();
        }
        if (do_overlays || settings.wireframe) {
            overlays.prepare();
            this._paint_levels(overlays.ctx, "overlay", frame_box, false);
            if (settings.wireframe) {
                this.paint_layout(overlays.ctx, this.layout);
            }
            overlays.finish();
        }
        if (this._initial_state.range == null) {
            this._initial_state.range = this._range_manager.compute_initial() ?? undefined;
        }
        for (const element_view of this.element_views) {
            element_view.reposition();
        }
        this._needs_paint = false;
        this.repainted.emit();
        logger.trace(`${this.toString()}._actual_paint ${this._render_count} end`);
        this._render_count++;
    }
    _paint_levels(ctx, level, clip_region, global_clip) {
        for (const renderer_view of this.computed_renderer_views) {
            if (renderer_view.model.level != level) {
                continue;
            }
            ctx.save();
            if (global_clip || renderer_view.needs_clip) {
                ctx.beginPath();
                ctx.rect(...clip_region);
                ctx.clip();
            }
            renderer_view.paint();
            ctx.restore();
            if (renderer_view.has_webgl) {
                this.canvas_view.blit_webgl(ctx);
            }
        }
    }
    paint_layout(ctx, layout) {
        const { x, y, width, height } = layout.bbox;
        ctx.strokeStyle = "blue";
        ctx.strokeRect(x, y, width, height);
        for (const child of layout) {
            ctx.save();
            if (!layout.absolute) {
                ctx.translate(x, y);
            }
            this.paint_layout(ctx, child);
            ctx.restore();
        }
    }
    _paint_empty(ctx, frame_box) {
        const [cx, cy, cw, ch] = [0, 0, this.bbox.width, this.bbox.height];
        const [fx, fy, fw, fh] = frame_box;
        if (this.visuals.border_fill.doit) {
            ctx.save();
            ctx.beginPath();
            ctx.rect(cx, cy, cw, ch);
            ctx.rect(fx, fy, fw, fh);
            ctx.clip("evenodd");
            ctx.beginPath();
            ctx.rect(cx, cy, cw, ch);
            this.visuals.border_fill.apply(ctx);
            ctx.restore();
        }
        if (this.visuals.background_fill.doit) {
            this.visuals.background_fill.set_value(ctx);
            ctx.fillRect(fx, fy, fw, fh);
        }
    }
    _paint_outline(ctx, frame_box) {
        if (this.visuals.outline_line.doit) {
            ctx.save();
            this.visuals.outline_line.set_value(ctx);
            let [x0, y0, w, h] = frame_box;
            // XXX: shrink outline region by 1px to make right and bottom lines visible
            // if they are on the edge of the canvas.
            if (x0 + w == this.bbox.width) {
                w -= 1;
            }
            if (y0 + h == this.bbox.height) {
                h -= 1;
            }
            ctx.strokeRect(x0, y0, w, h);
            ctx.restore();
        }
    }
    export(type = "auto", hidpi = true) {
        const output_backend = (() => {
            switch (type) {
                case "auto": return this.canvas_view.model.output_backend;
                case "png": return "canvas";
                case "svg": return "svg";
            }
        })();
        const composite = new CanvasLayer(output_backend, hidpi);
        const { width, height } = this.bbox;
        composite.resize(width, height);
        if (width != 0 && height != 0) {
            const { canvas } = this.canvas_view.compose();
            composite.ctx.drawImage(canvas, 0, 0);
        }
        return composite;
    }
    resolve_frame() {
        return this.frame; // TODO CartesianFrameView (PR #13286)
    }
    resolve_canvas() {
        return this.canvas;
    }
    resolve_plot() {
        return this;
    }
    resolve_xy(coord) {
        const { x, y } = coord;
        const sx = this.frame.x_scale.compute(x);
        const sy = this.frame.y_scale.compute(y);
        if (this.frame.bbox.contains(sx, sy)) {
            return { x: sx, y: sy };
        }
        else {
            return { x: NaN, y: NaN };
        }
    }
    resolve_indexed(coord) {
        const { index: i, renderer } = coord;
        const rv = this.views.find_one(renderer);
        if (rv != null && rv.has_finished()) {
            const [sx, sy] = rv.glyph.scenterxy(i, NaN, NaN);
            if (this.frame.bbox.contains(sx, sy)) {
                return { x: sx, y: sy };
            }
        }
        return { x: NaN, y: NaN };
    }
    _messages = new Map();
    notify_about(message) {
        if (this._messages.has(message)) {
            return;
        }
        const el = new Div({ children: [message] });
        const timer = setTimeout(() => {
            this._messages.delete(message);
            this._notifications.elements = this._notifications.elements.filter((item) => item != el);
        }, 2000);
        this._messages.set(message, timer);
        this._notifications.elements = [...this._notifications.elements, el];
        logger.info(message);
    }
}
//# sourceMappingURL=plot_canvas.js.map