import { HasProps } from "./has_props";
import { Signal0, Signal } from "./signaling";
import { isArray, isString, isNumber } from "./util/types";
import { Node } from "../models/coordinates/node";
import { XY as XY_ } from "../models/coordinates/xy";
import { Indexed } from "../models/coordinates/indexed";
import { ViewManager, ViewQuery } from "./view_manager";
import { equals } from "./util/eq";
export class View {
    static __name__ = "View";
    removed = new Signal0(this, "removed");
    model;
    parent;
    root;
    owner;
    views = new ViewQuery(this);
    _ready = Promise.resolve(undefined);
    get ready() {
        return this._ready;
    }
    /** @internal */
    _slots = new WeakMap();
    connect(signal, slot) {
        let new_slot = this._slots.get(slot);
        if (new_slot == null) {
            new_slot = (args, sender) => {
                const promise = Promise.resolve(slot.call(this, args, sender));
                this._ready = this._ready.then(() => promise);
                if (this.root != this) {
                    this.root._ready = this.root._ready.then(() => this._ready);
                }
            };
            this._slots.set(slot, new_slot);
        }
        return signal.connect(new_slot, this);
    }
    disconnect(signal, slot) {
        return signal.disconnect(slot, this);
    }
    constructor(options) {
        const { model, parent, owner } = options;
        this.model = model;
        this.parent = parent;
        if (parent == null) {
            this.root = this;
            this.owner = owner ?? new ViewManager([this]);
        }
        else {
            this.root = parent.root;
            this.owner = this.root.owner;
        }
    }
    initialize() { }
    async lazy_initialize() { }
    _destroyed = false;
    remove() {
        this.disconnect_signals();
        this.owner.remove(this);
        this.removed.emit();
        this._destroyed = true;
    }
    get is_destroyed() {
        return this._destroyed;
    }
    toString() {
        return `${this.model.type}View(${this.model.id})`;
    }
    [equals](that, _cmp) {
        return Object.is(this, that);
    }
    *children() { }
    _has_finished = false;
    mark_finished() {
        this._has_finished = true;
    }
    /**
     * Mark as finished even if e.g. external resources were not loaded yet.
     */
    force_finished() {
        this.mark_finished();
    }
    finish() {
        this.mark_finished();
        this.notify_finished();
    }
    _idle_notified = false;
    notify_finished() {
        if (!this.is_root) {
            this.root.notify_finished();
        }
        else {
            if (!this._idle_notified && this.has_finished()) {
                const { document } = this.model;
                if (document != null) {
                    this._idle_notified = true;
                    document.notify_idle(this.model);
                }
            }
        }
    }
    serializable_state() {
        const children = [...this.children()]
            .filter((view) => view.model.is_syncable)
            .map((view) => view.serializable_state())
            .filter((item) => item.bbox != null && item.bbox.is_valid && !item.bbox.is_empty);
        return {
            type: this.model.type,
            children,
        };
    }
    get is_root() {
        return this.parent == null;
    }
    has_finished() {
        return this._has_finished;
    }
    get is_idle() {
        return this.has_finished();
    }
    connect_signals() { }
    disconnect_signals() {
        Signal.disconnect_receiver(this);
    }
    on_change(properties, fn) {
        for (const property of isArray(properties) ? properties : [properties]) {
            this.connect(property.change, fn);
        }
    }
    on_transitive_change(property, fn) {
        const collect = () => {
            const value = property.is_unset ? [] : property.get_value();
            return HasProps.references(value, { recursive: false });
        };
        const connect = (models) => {
            for (const model of models) {
                this.connect(model.change, fn);
            }
        };
        const disconnect = (models) => {
            for (const model of models) {
                this.disconnect(model.change, fn);
            }
        };
        let models = collect();
        connect(models);
        this.on_change(property, () => {
            disconnect(models);
            models = collect();
            connect(models);
            fn();
        });
    }
    cursor(_sx, _sy) {
        return null;
    }
    resolve_frame() {
        return null;
    }
    resolve_canvas() {
        return null;
    }
    resolve_plot() {
        return null;
    }
    resolve_target(target) {
        if (isString(target)) {
            const ascend = (fn) => {
                let obj = this;
                while (obj != null) {
                    const view = fn(obj);
                    if (view != null) {
                        return view;
                    }
                    else {
                        obj = obj.parent;
                    }
                }
                return null;
            };
            switch (target) {
                case "parent": return this.parent;
                case "frame": return ascend((view) => view.resolve_frame());
                case "canvas": return ascend((view) => view.resolve_canvas());
                case "plot": return ascend((view) => view.resolve_plot());
            }
        }
        else {
            const queue = [this.root];
            while (true) {
                const child = queue.shift();
                if (child == null) {
                    break;
                }
                else if (child.model == target) {
                    return child;
                }
                else {
                    queue.push(...child.children());
                }
            }
            return null;
        }
    }
    resolve_symbol(_node) {
        return { x: NaN, y: NaN };
    }
    resolve_node(node) {
        const target = this.resolve_target(node.target);
        if (target != null) {
            return target.resolve_symbol(node);
        }
        else {
            return { x: NaN, y: NaN };
        }
    }
    resolve_coordinate(coord) {
        if (coord instanceof XY_) {
            let obj = this;
            while (obj != null && obj.resolve_xy == null) {
                obj = obj.parent;
            }
            return obj?.resolve_xy?.(coord) ?? { x: NaN, y: NaN };
        }
        else if (coord instanceof Indexed) {
            let obj = this;
            while (obj != null && obj.resolve_indexed == null) {
                obj = obj.parent;
            }
            return obj?.resolve_indexed?.(coord) ?? { x: NaN, y: NaN };
        }
        else if (coord instanceof Node) {
            return this.resolve_node(coord);
        }
        else {
            return { x: NaN, y: NaN };
        }
    }
    resolve_as_xy(coord) {
        const value = this.resolve_coordinate(coord);
        return isNumber(value) ? { x: NaN, y: NaN } : value;
    }
    resolve_as_scalar(coord, dim) {
        const value = this.resolve_coordinate(coord);
        return isNumber(value) ? value : value[dim];
    }
}
//# sourceMappingURL=view.js.map