import { Renderer, RendererView } from "./renderer";
import { UIElement } from "../ui/ui_element";
import { DOMNode } from "../dom/dom_node";
import { build_views, remove_views } from "../../core/build_views";
import { Ref, Or } from "../../core/kinds";
// TODO UIElement needs to inherit from DOMNode
const ElementLike = Or(Ref(UIElement), Ref(DOMNode));
export class CompositeRendererView extends RendererView {
    static __name__ = "CompositeRendererView";
    _renderer_views = new Map();
    get renderer_views() {
        return this.model.renderers.map((renderer) => this._renderer_views.get(renderer));
    }
    _element_views = new Map();
    get element_views() {
        return this.model.elements.map((element) => this._element_views.get(element));
    }
    *children() {
        yield* super.children();
        yield* this.renderer_views;
        yield* this.element_views;
    }
    async lazy_initialize() {
        await super.lazy_initialize();
        await this._build_renderers();
        await this._build_elements();
    }
    _computed_renderers = [];
    get computed_renderers() {
        return [...this.model.renderers, ...this._computed_renderers];
    }
    get computed_renderer_views() {
        return this.computed_renderers.map((item) => this._renderer_views.get(item)).filter((rv) => rv != null);
    }
    async _build_renderers() {
        return await build_views(this._renderer_views, this.computed_renderers, { parent: this.plot_view });
    }
    _computed_elements = [];
    get computed_elements() {
        return [...this.model.elements, ...this._computed_elements];
    }
    get computed_element_views() {
        return this.computed_elements.map((item) => this._element_views.get(item)).filter((ev) => ev != null);
    }
    async _build_elements() {
        return await build_views(this._element_views, this.computed_elements, { parent: this.plot_view });
    }
    async _update_renderers() {
        await this._build_renderers();
    }
    async _update_elements() {
        const { created } = await this._build_elements();
        const created_elements = new Set(created);
        // First remove and then either reattach existing elements or render and
        // attach new elements, so that the order of children is consistent, while
        // avoiding expensive re-rendering of existing views.
        for (const element_view of this.element_views) {
            element_view.el.remove();
        }
        for (const element_view of this.element_views) {
            const is_new = created_elements.has(element_view);
            const target = element_view.rendering_target() ?? this.shadow_el;
            if (is_new) {
                element_view.render_to(target);
            }
            else {
                target.append(element_view.el);
            }
        }
        this.r_after_render();
    }
    remove() {
        remove_views(this._renderer_views);
        remove_views(this._element_views);
        super.remove();
    }
    connect_signals() {
        super.connect_signals();
        const { renderers, elements } = this.model.properties;
        this.on_change(renderers, async () => {
            await this._update_renderers();
        });
        this.on_change(elements, async () => {
            await this._update_elements();
        });
    }
    _has_rendered_elements = false;
    paint() {
        if (!this._has_rendered_elements) {
            for (const element_view of this.element_views) {
                const target = element_view.rendering_target() ?? this.shadow_el;
                element_view.render_to(target);
            }
            this._has_rendered_elements = true;
        }
        super.paint();
        if (this.displayed && this.is_renderable) {
            for (const renderer of this.computed_renderer_views) {
                renderer.paint();
            }
        }
        const { displayed } = this;
        for (const element_view of this.element_views) {
            element_view.reposition(displayed);
        }
    }
    has_finished() {
        if (!super.has_finished()) {
            return false;
        }
        for (const renderer_view of this.renderer_views) {
            if (!renderer_view.has_finished()) {
                return false;
            }
        }
        for (const element_view of this.element_views) {
            if (!element_view.has_finished()) {
                return false;
            }
        }
        return true;
    }
}
export class CompositeRenderer extends Renderer {
    static __name__ = "CompositeRenderer";
    constructor(attrs) {
        super(attrs);
    }
    static {
        this.define(({ List, Ref }) => ({
            renderers: [List(Ref(Renderer)), []],
            elements: [List(ElementLike), []],
        }));
    }
}
//# sourceMappingURL=composite_renderer.js.map