"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Component = void 0;
const NOOP_1 = require("../NOOP");
const proxyWrap_1 = require("../proxyWrap");
const UCGEE_1 = require("../types/interface/UCGEE");
const array_1 = require("../util/array");
const callAll_1 = require("../util/call/callAll");
const control_1 = require("../util/control");
const element_1 = require("../util/element");
const object_1 = require("../util/object");
const weakMerge_1 = require("../weakMerge");
const DEFAULT_FONT_SIZE_1 = require("./DEFAULT_FONT_SIZE");
const activeElement_1 = require("./activeElement");
const addListener_1 = require("./addListener");
const animateSimulate_1 = require("./animation/animateSimulate");
const color_1 = require("./color");
const ANIMATION_PROPERTY_DELTA_PAIRS_1 = require("./component/app/graph/ANIMATION_PROPERTY_DELTA_PAIRS");
const namespaceURI_1 = require("./component/namespaceURI");
const componentFromSpecId_1 = require("./componentFromSpecId");
const context_1 = require("./context");
const custom_1 = require("./event/custom");
const drag_1 = require("./event/drag");
const extractTrait_1 = require("./extractTrait");
const interface_1 = require("./interface");
const makeEventListener_1 = require("./makeEventListener");
const mount_1 = require("./mount");
const rawExtractStyle_1 = require("./rawExtractStyle");
const reflectComponentBaseTrait_1 = require("./reflectComponentBaseTrait");
const stopPropagation_1 = require("./stopPropagation");
const style_1 = require("./style");
const unmount_1 = require("./unmount");
const geometry_1 = require("./util/geometry");
const getFontSize_1 = require("./util/style/getFontSize");
const getOpacity_1 = require("./util/style/getOpacity");
const getPosition_1 = require("./util/style/getPosition");
const getRect_1 = require("./util/style/getRect");
const getScale_1 = require("./util/style/getScale");
const getSize_1 = require("./util/style/getSize");
const getTextAlign_1 = require("./util/style/getTextAlign");
const $childToComponent = (system, { bundle }) => {
    var _a;
    const { unit, specs = {} } = bundle;
    const { id, memory = { input: {} } } = unit;
    const props = {};
    (0, object_1.forEachObjKV)((_a = memory.input) !== null && _a !== void 0 ? _a : {}, (pinId, { _data }) => {
        props[pinId] = _data;
    });
    const component = (0, componentFromSpecId_1.componentFromSpecId)(system, (0, weakMerge_1.weakMerge)(specs, system.specs), id, props);
    return component;
};
class Component {
    get $context() {
        var _a;
        return (_a = this._$context) !== null && _a !== void 0 ? _a : this.$system.context[0];
    }
    constructor($props, $system, $element, $node) {
        this.$_ = [];
        this.$changed = new Set();
        this.$ref = {};
        this.$connected = false;
        this.$primitive = true;
        this.$propUnlisten = {};
        this.$unbundled = true;
        this.$controlled = false;
        this.$children = [];
        this.$slotChildren = { default: [] };
        this.$childSlotName = [];
        this.$remoteChildren = [];
        this.$slot = { default: this };
        this.$slotId = {};
        this.$slotTarget = {};
        this.$subComponent = {};
        this.$root = [];
        this.$wrap = false;
        this.$output = {};
        this.$parentRoot = [];
        this.$parentRootSlotName = [];
        this.$parentChildren = [];
        this.$parentChildrenSlot = [];
        this.$mountRoot = [];
        this.$mountParentRoot = [];
        this.$mountParentChildren = [];
        this.$rootParent = null;
        this.$parent = null;
        this.$slotParent = null;
        this.$detachedContext = null;
        this.$detachedSlotParent = null;
        this.$returnIndex = 0;
        this.$mounted = false;
        this._preventDefaultCounter = {};
        this._prop_transformer = {};
        this._attachTextUnlisten = {};
        this._addBaseListener = (event, listener, opt) => {
            const roots = this.getBaseRoots();
            const allUnlisten = [];
            for (const root of roots) {
                root.$element.addEventListener(event, listener, opt);
                allUnlisten.push(() => {
                    root.$element.removeEventListener(event, listener);
                });
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this._releasePointerCapture = {};
        this.$detached = false;
        this._animateBase = (base, hostSlot, reverse = false, prepend = false, commit) => {
            const { foreground: { html }, api: { window: { HTMLElement }, text: { measureText }, document: { createElement }, layout: { reflectChildrenTrait }, }, } = this.$system;
            const allAbort = [];
            let hostTarget = hostSlot;
            while (hostTarget.isParent() ||
                (hostTarget.$element instanceof HTMLElement && hostTarget.$element).style
                    .display === 'contents') {
                hostTarget = hostTarget.$slotParent;
            }
            const hostTrait = (0, extractTrait_1.extractTrait)(hostTarget, measureText);
            const hostStyle = (0, rawExtractStyle_1.rawExtractStyle)(hostTarget.$element, hostTrait, measureText);
            delete hostStyle['transform'];
            delete hostStyle['opacity'];
            const baseStyle = (0, reflectComponentBaseTrait_1.getBaseStyle)(base, [], (leafPath, leafComp) => {
                return (0, rawExtractStyle_1.rawExtractStyle)(leafComp.$element, hostTrait, measureText);
            });
            const targetTraits = reflectChildrenTrait(hostTrait, hostStyle, baseStyle, [], () => {
                return [];
            });
            let leafFinished = 0;
            let leafFrames = [];
            for (let i = 0; i < base.length; i++) {
                const targetTrait = targetTraits[i];
                const leaf = base[i];
                const [_, leafComp] = leaf;
                const leafTrait = (0, extractTrait_1.extractTrait)(leafComp, measureText);
                const leafFrame = createElement('div');
                (0, style_1.applyStyle)(leafFrame, {
                    position: 'absolute',
                    left: `${leafTrait.x}px`,
                    top: `${leafTrait.y}px`,
                    width: `${leafTrait.width}px`,
                    height: `${leafTrait.height}px`,
                    pointerEvents: 'none',
                    zIndex: '0',
                    // border: `1px solid ${randomColorString()}`,
                });
                if (prepend) {
                    hostTarget.$element.prepend(leafFrame);
                }
                else {
                    hostTarget.$element.appendChild(leafFrame);
                }
                !reverse && this.domRemoveLeaf(leaf);
                leafComp.unmount();
                leafFrame.appendChild(leafComp.$element);
                leafComp.$slotParent = hostTarget;
                leafComp.mount(hostTarget._$context);
                leafFrames.push(leafFrame);
                const { x: scrollX0, y: scrollY0 } = (0, getPosition_1.getScrollPosition)(hostTarget.$element, hostTarget._$context.$element);
                const abortAnimation = (0, animateSimulate_1.animateSimulate)(this.$system, leafTrait, () => {
                    return targetTrait;
                }, ANIMATION_PROPERTY_DELTA_PAIRS_1.ANIMATION_PROPERTY_DELTA_PAIRS, ({ x, y, width, height, sx, sy, opacity, fontSize }) => {
                    const { x: scrollX, y: scrollY } = (0, getPosition_1.getScrollPosition)(hostTarget.$element, hostTarget._$context.$element);
                    const scrollDx = scrollX - scrollX0;
                    const scrollDy = scrollY - scrollY0;
                    leafFrame.style.left = `${x -
                        (reverse ? this._$context.$x : hostTrait.x) +
                        ((Math.abs(sx) - 1) * width) / 2 -
                        scrollDx}px`;
                    leafFrame.style.top = `${y -
                        (reverse ? this._$context.$y : hostTrait.y) +
                        ((Math.abs(sy) - 1) * height) / 2 -
                        scrollDy}px`;
                    leafFrame.style.width = `${width}px`;
                    leafFrame.style.height = `${height}px`;
                    leafFrame.style.transform = `scale(${sx}, ${sy})`;
                    // leafFrame.style.opacity = `${opacity}`
                    leafFrame.style.opacity = `1`;
                    leafFrame.style.fontSize = `${fontSize}px`;
                }, () => {
                    leafFinished++;
                    if (leafFinished === base.length) {
                        for (const leafFrame of leafFrames) {
                            leafFrame.removeChild(leafComp.$element);
                            hostTarget.$element.removeChild(leafFrame);
                        }
                        commit();
                    }
                });
                allAbort.push(abortAnimation);
            }
            return (0, callAll_1.callAll)(allAbort);
        };
        this.templateBase = (base, root, parentRoot, self) => {
            // console.log('Component', 'templateBase', sub_component_id)
            for (const leaf of base) {
                this.templateLeaf(leaf, root, parentRoot, self);
            }
        };
        this.templateLeaf = (leaf, root, parentRoot, self) => {
            // console.log('Component', 'templateLeaf', sub_component_id)
            const [leafPath, leafComp] = leaf;
            const leafParentLast = leafPath[leafPath.length - 1];
            const leafParentPath = leafPath.slice(0, -1);
            const leafParent = this.pathGetSubComponent(leafParentPath);
            if (leafParent === leafComp) {
                self(leafComp);
            }
            else {
                const parent_id = leafParent.getSubComponentParentId(leafParentLast);
                if (parent_id) {
                    const parent = leafParent.getSubComponent(parent_id);
                    parentRoot(parent, leafComp);
                }
                else {
                    root(leafParent, leafComp);
                }
            }
        };
        this.domRemoveBase = (base) => {
            this.templateBase(base, (parent, leafComp) => {
                const index = parent.$root.indexOf(leafComp);
                parent.domRemoveRoot(leafComp, index);
            }, (parent, leafComp) => {
                const index = parent.$parentRoot.indexOf(leafComp);
                parent.domRemoveParentRootAt(leafComp, 'default', index, index, this.$slotParent);
            }, (leafComp) => {
                //
            });
        };
        this.domRemoveLeaf = (leaf) => {
            this.templateLeaf(leaf, (parent, leaf_comp) => {
                const index = parent.$root.indexOf(leaf_comp);
                parent.domRemoveRoot(leaf_comp, index);
            }, (parent, leaf_comp) => {
                const index = parent.$parentRoot.indexOf(leaf_comp);
                parent.domRemoveParentRootAt(leaf_comp, 'default', index, index, this.$slotParent);
            }, () => {
                //
            });
        };
        this.domAppendBase = (base, fallbackParent) => {
            // console.log('Component', 'domAppendBase', sub_component_id)
            this.templateBase(base, (parent, leafComp) => {
                parent.domAppendRoot(leafComp, this.$root.length - 1);
            }, (parent, leafComp) => {
                const index = parent.$parentRoot.indexOf(leafComp);
                parent.domAppendParentRoot(leafComp, 'default', index);
            }, () => {
                //
            });
        };
        this.$listener = [];
        this.$unlisten = [];
        this.$named_listener_count = {};
        this.$named_unlisten = {};
        this._addUnitEventListener = (event) => {
            if (event.startsWith('_')) {
                const _event = event.slice(1);
                const unlisten = this.addEventListener((0, custom_1.makeCustomListener)(_event, (data) => {
                    const $emitter = this.$unit.$refEmitter({ _: ['EE'] });
                    $emitter.$emit({ type: event, data }, NOOP_1.NOOP);
                }));
                this.$named_unlisten[event] = unlisten;
            }
            else {
                const unlisten = this.addEventListener((0, makeEventListener_1.makeUIEventListener)(event, (data) => {
                    const $emitter = this.$unit.$refEmitter({ _: ['EE'] });
                    $emitter.$emit({ type: event, data }, NOOP_1.NOOP);
                }));
                this.$named_unlisten[event] = unlisten;
            }
        };
        this._removeUnitEventListener = (event) => {
            const unlisten = this.$named_unlisten[event];
            delete this.$named_unlisten[event];
            unlisten();
        };
        this._instanceChild = ($child, at) => {
            const component = $childToComponent(this.$system, $child);
            const _ = (0, interface_1.getComponentInterface)(component);
            const $childRef = this.$unit.$refChild({ at, _ });
            component.connect($childRef);
            return component;
        };
        this.$control = ($emitter) => {
            return (0, callAll_1.callAll)([
                $emitter.$addListener({ event: 'set_sub_component' }, ([subComponentId, bundle]) => {
                    if (!this.$controlled) {
                        const child = $childToComponent(this.$system, { bundle });
                        const _ = (0, interface_1.getComponentInterface)(child);
                        const $subComponent = this.$unit.$refUnit({
                            unitId: subComponentId,
                            _,
                        });
                        child.connect($subComponent);
                        this.setSubComponent(subComponentId, child);
                    }
                }),
                $emitter.$addListener({ event: 'register_root' }, ([globalRef]) => {
                    if (!this.$controlled) {
                        const { globalId } = globalRef;
                        let subComponent;
                        for (const subComponentId in this.$subComponent) {
                            const siblingComponent = this.$subComponent[subComponentId];
                            if (siblingComponent.$remoteId === globalId) {
                                subComponent = siblingComponent;
                                break;
                            }
                        }
                        if (subComponent) {
                            if (subComponent.$rootParent) {
                                subComponent.$rootParent.removeParentRoot(subComponent);
                            }
                            this.registerRoot(subComponent);
                        }
                    }
                }),
                $emitter.$addListener({ event: 'unregister_root' }, ([globalRef]) => {
                    if (!this.$controlled) {
                        const { globalId } = globalRef;
                        let subComponent;
                        for (const subComponentId in this.$subComponent) {
                            const siblingComponent = this.$subComponent[subComponentId];
                            if (siblingComponent.$remoteId === globalId &&
                                siblingComponent.$mounted) {
                                subComponent = siblingComponent;
                                break;
                            }
                        }
                        if (subComponent) {
                            for (const parentRoot of [...subComponent.$parentRoot]) {
                                subComponent.unregisterParentRoot(parentRoot);
                                this.registerRoot(parentRoot);
                            }
                            this.unregisterRoot(subComponent);
                        }
                    }
                }),
                $emitter.$addListener({ event: 'register_parent_root' }, ([globalRef]) => {
                    if (!this.$controlled) {
                        const { globalId } = globalRef;
                        const components = this.$system.getLocalComponents(globalId);
                        if (!components.length) {
                            return;
                        }
                        let subComponent;
                        if (!this.$parent.$controlled) {
                            for (const subComponentId in this.$parent.$subComponent) {
                                const siblingComponent = this.$parent.$subComponent[subComponentId];
                                if (siblingComponent.$remoteId === globalId) {
                                    subComponent = siblingComponent;
                                    break;
                                }
                            }
                            if (subComponent) {
                                if (subComponent.$rootParent) {
                                    subComponent.$rootParent.removeParentRoot(subComponent);
                                }
                                else if (subComponent.$parent &&
                                    subComponent.$parent.$mountRoot.includes(subComponent)) {
                                    subComponent.$parent.removeRoot(subComponent);
                                }
                                this.registerParentRoot(subComponent);
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'unregister_parent_root' }, ([globalRef]) => {
                    if (!this.$controlled) {
                        const { globalId } = globalRef;
                        const components = this.$system.getLocalComponents(globalId);
                        if (!components.length) {
                            return;
                        }
                        let subComponent;
                        if (!this.$parent.$controlled) {
                            for (const subComponentId in this.$parent.$subComponent) {
                                const siblingComponent = this.$parent.$subComponent[subComponentId];
                                if (siblingComponent.$remoteId === globalId) {
                                    subComponent = siblingComponent;
                                    break;
                                }
                            }
                            if (subComponent) {
                                if (subComponent.$rootParent) {
                                    subComponent.$rootParent.removeParentRoot(subComponent);
                                }
                                else if (subComponent.$parent &&
                                    subComponent.$parent.$mountRoot.includes(subComponent)) {
                                    subComponent.$parent.removeRoot(subComponent);
                                }
                                this.unregisterParentRoot(subComponent);
                            }
                        }
                    }
                }),
                $emitter.$addListener({ event: 'reorder_sub_component' }, ([parentId, childId, to]) => {
                    if (!this.$controlled) {
                        const child = this.getSubComponent(childId);
                        if (parentId) {
                            const parent = this.getSubComponent(parentId);
                            const slotName = 'default';
                            parent.removeParentRoot(child);
                            parent.insertParentChildAt(child, slotName, to);
                        }
                        else {
                            this.removeRoot(child);
                            this.insertRootAt(child, to);
                        }
                    }
                }),
                $emitter.$addListener({ event: 'move_sub_component_root' }, ([parentId, prevParentMap, children, slotMap, prevSlotMap]) => {
                    if (!this.$controlled) {
                        for (const childId of children) {
                            const child = this.getSubComponent(childId);
                            const currentParentId = this.getSubComponentParentId(childId);
                            if (currentParentId) {
                                const parent = this.getSubComponent(currentParentId);
                                parent.unregisterParentRoot(child);
                            }
                            else {
                                this.unregisterRoot(child);
                            }
                            if (parentId) {
                                const parent = this.getSubComponent(parentId);
                                const slotName = 'default';
                                parent.registerParentRoot(child, slotName);
                            }
                            else {
                                this.registerRoot(child);
                            }
                        }
                    }
                }),
            ]);
        };
        this.isParent = () => {
            var _a;
            const { api: { window: { HTMLElement }, }, } = this.$system;
            if (this.$element instanceof HTMLElement) {
                return !!((_a = this.$element.classList) === null || _a === void 0 ? void 0 : _a.contains('__parent'));
            }
            return false;
        };
        this._domCommitChild__template = (component, at, callback) => {
            if (component.isParent()) {
                let i = 0;
                for (const root of component.$root) {
                    this._domCommitChild__template(root, at + i, callback);
                    i++;
                }
                return;
            }
            if (this.isParent()) {
                if (this.$slotParent) {
                    let index = this.$slotParent.$mountParentChildren.indexOf(this);
                    this.$slotParent._domCommitChild__template(component, index + at, callback);
                }
                else {
                    callback(this.$element, component.$element, at);
                }
            }
            else {
                callback(this.$element, component.$element, at);
            }
        };
        this.addEventListener = (listener) => {
            const roots = this.getBaseRoots();
            const allUnlisten = [];
            for (const root of roots) {
                const unlisten = listener(root);
                allUnlisten.push(unlisten);
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this.addEventListeners = (listeners) => {
            const roots = this.getBaseRoots();
            const allUnlisten = [];
            for (const root of roots) {
                const unlisten = (0, addListener_1.addListeners)(root, listeners);
                allUnlisten.push(unlisten);
            }
            return (0, callAll_1.callAll)(allUnlisten);
        };
        this.$props = $props;
        this.$system = $system;
        this.$element = $element;
        this._$node = $node !== null && $node !== void 0 ? $node : $element;
    }
    get $node() {
        var _a;
        return (_a = this._$node) !== null && _a !== void 0 ? _a : this.$element;
    }
    set $node($node) {
        this._$node = $node;
    }
    getProp(name) {
        return this.$props[name];
    }
    setProp(prop, data) {
        this.$props[prop] = data;
        if (this.$mounted) {
            this._onPropChanged(prop, data);
        }
        else {
            this.$changed.add(prop);
        }
    }
    interceptProp(prop, transformer) {
        this._prop_transformer[prop] = this._prop_transformer[prop] || [];
        this._prop_transformer[prop].push(transformer);
        return () => {
            (0, array_1.remove)(this._prop_transformer[prop], transformer);
        };
    }
    getSlot(slotName) {
        return this.$slot[slotName];
    }
    onPropChanged(prop, current) { }
    _onPropChanged(prop, current) {
        const _current = (this._prop_transformer[prop] || []).reduce((acc, t) => {
            return t(acc);
        }, current);
        this.onPropChanged(prop, _current !== null && _current !== void 0 ? _current : current);
    }
    refreshProp(prop) {
        this._onPropChanged(prop, this.$props[prop]);
    }
    onConnected($unit) { }
    onDisconnected() { }
    onMount() { }
    onUnmount($context) { }
    onDestroy() {
        for (const child of this.$children) {
            child.destroy();
        }
    }
    dispatchEvent(type, detail = {}, bubbles = true) {
        for (const root of this.getBaseRoots()) {
            (0, context_1.dispatchCustomEvent)(root.$element, type, detail, bubbles);
        }
    }
    dispatchContextEvent(type, data = {}) {
        if (this._$context) {
            (0, context_1.dispatchContextEvent)(this._$context, type, data);
        }
    }
    stopPropagation(event) {
        var _a;
        this._stopPropagationSet = (_a = this._stopPropagationSet) !== null && _a !== void 0 ? _a : new Set();
        if (this._stopPropagationSet.has(event)) {
            return;
        }
        this._stopPropagationSet.add(event);
        return this._addBaseListener(event, stopPropagation_1.stopPropagation, { passive: true });
    }
    cancelStopPropagation(event) {
        if (!this._stopPropagationSet.has(event)) {
            return;
        }
        this._stopPropagationSet.delete(event);
        const base = this.getRootBase();
        for (const [_, leaf] of base) {
            leaf.$element.removeEventListener(event, stopPropagation_1.stopPropagation);
        }
    }
    stopImmediatePropagation(event) {
        var _a;
        this._stopImmediatePropagationSet =
            (_a = this._stopImmediatePropagationSet) !== null && _a !== void 0 ? _a : new Set();
        if (this._stopImmediatePropagationSet.has(event)) {
            return;
        }
        this._stopImmediatePropagationSet.add(event);
        this.$element.addEventListener(event, stopPropagation_1.stopImmediatePropagation, {
            passive: true,
        });
    }
    attachText(type, text) {
        if (this._attachTextUnlisten[type]) {
            this.detachText(type);
        }
        const base = this.getRootBase();
        const unlistenAll = [];
        for (const [_, leaf] of base) {
            const listener = (event) => {
                event.dataTransfer.setData(type, text);
            };
            leaf.$element.addEventListener('dragstart', listener);
            unlistenAll.push(() => {
                leaf.$element.removeEventListener('dragstart', listener);
            });
        }
        this._attachTextUnlisten[type] = (0, callAll_1.callAll)(unlistenAll);
    }
    detachText(type) {
        const unlisten = this._attachTextUnlisten[type];
        if (unlisten) {
            unlisten();
        }
    }
    attachDropTarget() {
        const rootLeaf = this.getFirstRootLeaf();
        rootLeaf.$element.addEventListener('dragover', (event) => {
            event.preventDefault();
        });
        rootLeaf.$element.addEventListener('drop', async (event) => {
            event.preventDefault();
            const { dataTransfer } = event;
            const { items } = dataTransfer;
            const promises = [];
            for (let i = 0; i < items.length; i++) {
                const item = items[i];
                promises.push((0, drag_1.readDataTransferItemAsText)(item));
            }
            const texts = await Promise.all(promises);
            if (this.$unit) {
                const $emitter = this.$unit.$refEmitter({ _: ['EE'] });
                $emitter.$emit({ type: 'drop', data: texts }, NOOP_1.NOOP);
            }
        });
    }
    preventDefault(event) {
        var _a;
        this._preventDefaultCounter[event] = (_a = this._preventDefaultCounter[event]) !== null && _a !== void 0 ? _a : 0;
        let unlisten = NOOP_1.NOOP;
        const unlisten_ = () => {
            this._preventDefaultCounter[event]--;
            if (this._preventDefaultCounter[event] === 0) {
                unlisten();
            }
        };
        this._preventDefaultCounter[event]++;
        if (this._preventDefaultCounter[event] === 1) {
            const listener = (event) => {
                event.preventDefault();
                return false;
            };
            const opt = { passive: false };
            unlisten = this._addBaseListener(event, listener, opt);
        }
        return unlisten_;
    }
    hasPointerCapture(pointerId) {
        return !!this._releasePointerCapture[pointerId];
    }
    setPointerCapture(pointerId) {
        const { api: { window: { Text }, input: { pointer: { setPointerCapture }, }, }, } = this.$system;
        // console.log(this.constructor.name, 'setPointerCapture', pointerId)
        const leaf = this.getFirstRootLeaf();
        if (leaf.$node instanceof Text) {
            throw new Error('cannot set pointer capture on a text element');
        }
        this._releasePointerCapture[pointerId] = setPointerCapture(leaf.$node, pointerId);
    }
    releasePointerCapture(pointerId) {
        // console.log(this.constructor.name, 'releasePointerCapture', pointerId)
        const release = this._releasePointerCapture[pointerId];
        if (release) {
            release();
            delete this._releasePointerCapture[pointerId];
        }
        else {
            // throw new Error('element is not capturing pointer')
        }
    }
    scroll(opt) {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const leaf = this.getFirstRootLeaf();
        if (leaf && leaf.$element instanceof HTMLElement) {
            leaf.$element.scroll(opt);
        }
    }
    scrollIntoView(opt) {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        if (this.$slot['default'] === this) {
            if (this.$primitive) {
                if (this.$element instanceof HTMLElement) {
                    this.$element.scrollIntoView(opt);
                }
            }
            else {
                if (this.$root.length > 0) {
                    this.$root[0].scrollIntoView(opt);
                }
            }
        }
        else {
            this.$slot['default'].scrollIntoView(opt);
        }
    }
    togglePopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.togglePopover) {
                firstLeaf.$element.togglePopover();
            }
        }
        else {
            //
        }
    }
    hidePopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.hidePopover) {
                firstLeaf.$element.hidePopover();
            }
        }
        else {
            //
        }
    }
    showPopover() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.showPopover) {
                firstLeaf.$element.showPopover();
            }
        }
        else {
            //
        }
    }
    register(remoteId) {
        if (this.$remoteId) {
            throw new Error('cannot register a registered component');
        }
        const { registerLocalComponent } = this.$system;
        this.$remoteId = remoteId;
        registerLocalComponent(this, remoteId);
    }
    unregister() {
        if (!this.$remoteId) {
            return;
        }
        this.$remoteId = undefined;
        const { unregisterLocalComponent } = this.$system;
        unregisterLocalComponent(this, this.$remoteId);
    }
    isFocusInside() {
        let activeElementInside = false;
        const activeElement = (0, activeElement_1.getActiveElement)(this.$system);
        if (activeElement) {
            activeElementInside = this.$element.contains(activeElement);
            for (const $root of this.$root) {
                activeElementInside = activeElementInside || $root.isFocusInside();
            }
        }
        return activeElementInside;
    }
    detach(host, opt) {
        // console.log('Component', 'detach', host, opt)
        const { api: { window: { HTMLElement, SVGElement }, }, } = this.$system;
        const { getLocalComponents } = this.$system;
        const { animate = false, prepend = false } = opt;
        if (this.$detached) {
            throw new Error('component is already detached');
        }
        this.$detached = true;
        if (this._baseAnimationAbort) {
            this._baseAnimationAbort();
            this._baseAnimationAbort = undefined;
        }
        const hostGlobalId = host.replace('unit://', '');
        const hosts = getLocalComponents(hostGlobalId);
        if (hosts.length === 0) {
            return;
        }
        const activeElementInside = this.isFocusInside();
        const activeElement = (0, activeElement_1.getActiveElement)(this.$system);
        const hostComponent = hosts[0]; // TODO heuristic: get closest on the tree
        let hostSlot = hostComponent.getSlot('default');
        const base = this.getRootBase();
        const commit = () => {
            for (const [_, leaf] of base) {
                hostSlot.domAppendChild(leaf);
            }
            if (activeElementInside) {
                if (activeElement instanceof HTMLElement ||
                    activeElement instanceof SVGElement) {
                    activeElement.focus();
                }
            }
        };
        this.$detachedSlotParent = this.$slotParent;
        this.$detachedContext = this._$context;
        let index = 0;
        if (this.$rootParent) {
            index = this.$rootParent.$parentRoot.indexOf(this);
        }
        else if (this.$parent) {
            index = this.$parent.$parentRoot.indexOf(this);
        }
        else {
            index = this._$context.$children.indexOf(this);
        }
        this.$returnIndex = index;
        if (animate) {
            this._baseAnimationAbort = this._animateBase(base, hostSlot, false, prepend, commit);
        }
        else {
            this.domRemoveBase(base);
            commit();
        }
    }
    attach(opt) {
        // console.log('Component', 'attach', opt)
        if (!this.$detached) {
            throw new Error('component is not detached');
        }
        const { animate } = opt;
        this.$detached = false;
        if (this._baseAnimationAbort) {
            this._baseAnimationAbort();
            this._baseAnimationAbort = undefined;
        }
        const base = this.getRootBase();
        let leafEnd = 0;
        const couple = (leafComp) => {
            leafComp.unmount();
            if (leafComp.$rootParent) {
                leafComp.$rootParent.domInsertParentRootAt(leafComp, this.$returnIndex, 'default');
            }
            else if (leafComp.$parent) {
                leafComp.$parent.domInsertRootAt(leafComp, this.$returnIndex);
            }
            else {
                //
            }
            leafComp.mount(this.$parent._$context);
        };
        if (animate) {
            const targetSlots = [];
            this.templateBase(base, (root) => {
                targetSlots.push(root.$slot['default']);
            }, (parentRoot) => {
                targetSlots.push(parentRoot.$slot['default']);
            }, () => {
                //
            });
            for (let i = 0; i < base.length; i++) {
                const leaf = base[i];
                const [_, leafComp] = leaf;
                const targetSlot = this.$detachedSlotParent;
                this._baseAnimationAbort = this._animateBase([leaf], targetSlot, true, false, () => {
                    leafEnd++;
                    couple(leafComp);
                });
            }
        }
        else {
            for (let i = 0; i < base.length; i++) {
                const leaf = base[i];
                const [_, leafComp] = leaf;
                couple(leafComp);
            }
        }
    }
    mountChild(child, commit = true) {
        child.mount(this._$context, commit);
    }
    unmountDescendent(child) {
        return child.unmount();
    }
    _forAllMountDescendent(callback) {
        for (const subComponent of this.$mountRoot) {
            callback(subComponent);
        }
        for (const subComponent of this.$mountParentChildren) {
            callback(subComponent);
        }
    }
    _forAllDescendent(callback) {
        for (const subComponent of this.$root) {
            callback(subComponent);
        }
        for (const subComponent of this.$parentChildren) {
            callback(subComponent);
        }
    }
    commit() {
        if (this.$detached) {
            return;
        }
        this.onMount();
        this._forAllMountDescendent((child) => {
            child.commit();
        });
        const $changed = new Set(this.$changed);
        for (const name of $changed) {
            const current = this.$props[name];
            this.$changed.delete(name);
            this._onPropChanged(name, current);
        }
    }
    mount(_$context, commit = true) {
        // console.log(this.constructor.name, 'mount')
        if (this.$mounted) {
            throw new Error('cannot mount a mounted component');
        }
        this._$context = _$context;
        this.$mounted = true;
        this._forAllMountDescendent((child) => {
            if (child.$detached) {
                return;
            }
            this.mountChild(child, false);
        });
        if (commit) {
            this.commit();
        }
        if (this.isBase()) {
            this.dispatchEvent('mount', {}, false);
        }
    }
    unmount() {
        // console.log(this.constructor.name, 'unmount')
        if (!this.$mounted) {
            throw new Error('cannot unmount unmounted component');
        }
        const _$context = this._$context;
        this._forAllMountDescendent((child) => {
            if (child.$detached) {
                return;
            }
            this.unmountDescendent(child);
        });
        this.$mounted = false;
        this._$context = null;
        this.onUnmount(_$context);
        if (this.isBase()) {
            this.dispatchEvent('unmount', {}, false);
        }
    }
    focus(options = { preventScroll: true }) {
        const { api: { window: { Text }, }, } = this.$system;
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                return;
            }
            if (this.$primitive) {
                this.$element.focus(options);
            }
            else {
                if (this.$root.length > 0) {
                    this.$root[0].focus(options);
                }
            }
        }
        else {
            this.$slot['default'].focus(options);
        }
    }
    blur() {
        const { api: { window: { Text }, }, } = this.$system;
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                return;
            }
            if (!this.$primitive) {
                let i = 0;
                while (this.$root[i] instanceof Text) {
                    i++;
                }
                if (this.$mountRoot[i]) {
                    this.$mountRoot[i].blur();
                }
            }
            else {
                this.$element.blur();
            }
        }
        else {
            this.$slot['default'].blur();
        }
    }
    deselect() {
        const { api: { selection: { removeSelection }, }, } = this.$system;
        if (this.containsSelection()) {
            removeSelection();
        }
    }
    containsSelection() {
        const { api: { selection: { containsSelection }, }, } = this.$system;
        if (!this.$primitive) {
            return this.$root.some((root) => root.containsSelection());
        }
        else {
            return containsSelection(this.$element);
        }
    }
    animate(keyframes, options) {
        const base = this.getAnimatableBase();
        const animations = [];
        for (const leaf of base) {
            const animation = leaf.$element.animate(keyframes, options);
            animations.push(animation);
        }
        return animations;
    }
    getAnimatableBase() {
        const { api: { window: { Text }, }, } = this.$system;
        const base = this.getRootBase();
        const root = [];
        for (const [_, leaf] of base) {
            if (leaf.$element instanceof Text) {
                continue;
            }
            root.push(leaf);
        }
        return root;
    }
    getAnimationsById(id) {
        const animations = this.getAnimations();
        const animations_ = animations.filter((a) => {
            return a.id === id;
        });
        return animations_;
    }
    getAnimations() {
        const base = this.getAnimatableBase();
        const animations = [];
        for (const leaf of base) {
            const leafAnimations = leaf.$element.getAnimations();
            for (const animation of leafAnimations) {
                animations.push(animation);
            }
        }
        return animations;
    }
    cancelAnimation(id) {
        const animations = this.getAnimationsById(id);
        for (const animation of animations) {
            animation.cancel();
        }
    }
    commitAnimation(id) {
        const animations = this.getAnimationsById(id);
        for (const animation of animations) {
            animation.commitStyles();
        }
    }
    getOffset() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        let offset = this;
        if (this.isSVG()) {
            return offset;
        }
        while (offset &&
            !(offset.$element instanceof HTMLElement &&
                offset.$element.style.display !== 'contents' &&
                (offset.$element.style.position === '' ||
                    offset.$element.style.position === 'relative' ||
                    offset.$element.style.position === 'absolute'))) {
            offset = offset.$slotParent;
        }
        return offset;
    }
    getElementPosition() {
        const context_position = {
            x: this.$context.$x,
            y: this.$context.$y,
        };
        if (!this.$primitive) {
            throw new Error('cannot calculate position of multiple elements');
        }
        const relative_position = (0, getPosition_1.getRelativePosition)(this.$element, this.$context.$element);
        const position = (0, geometry_1.addVector)(context_position, relative_position);
        return position;
    }
    getElementSize() {
        if (!this.$primitive) {
            throw new Error('cannot calculate size of multiple elements');
        }
        return (0, getSize_1.getSize)(this.$element);
    }
    getColor() {
        const { api: { window: { HTMLElement, SVGElement }, }, } = this.$system;
        const defaultColor = () => {
            if (this.$slotParent) {
                return this.$slotParent.getColor();
            }
            else {
                return (0, color_1.hexToRgba)(this.$context.$color);
            }
        };
        if (this.$primitive) {
            if (this.$element instanceof HTMLElement ||
                this.$element instanceof SVGElement) {
                const styleColor = this.$element.style.color;
                if (styleColor) {
                    const hex = (0, color_1.colorToHex)(styleColor);
                    return (hex && (0, color_1.hexToRgba)(hex)) || defaultColor();
                }
                else {
                    return defaultColor();
                }
            }
            else {
                return defaultColor();
            }
        }
        else {
            return defaultColor();
        }
    }
    getFontSize() {
        if (this.$primitive) {
            const fontSize = (0, getFontSize_1.getFontSize)(this.$element);
            if (fontSize) {
                return fontSize;
            }
        }
        if (this.$slotParent) {
            return this.$slotParent.getFontSize();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_FONT_SIZE;
    }
    getOpacity() {
        if (this.$primitive) {
            const opacity = (0, getOpacity_1.getOpacity)(this.$element);
            if (opacity !== undefined) {
                return opacity;
            }
        }
        if (this.$slotParent) {
            return this.$slotParent.getOpacity();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_OPACITY;
    }
    getScale() {
        let scale = { sx: 1, sy: 1 };
        if (this.$primitive) {
            scale = (0, getScale_1.getScale)(this.$element);
        }
        if (this.$slotParent) {
            const parentScale = this.$slotParent.getScale();
            return { sx: scale.sx * parentScale.sx, sy: scale.sy * parentScale.sy };
        }
        return scale;
    }
    getTextAlign() {
        if (this.$primitive) {
            const textAlign = (0, getTextAlign_1.getTextAlign)(this.$element);
            if (textAlign) {
                return textAlign;
            }
        }
        if (this.$parent) {
            return this.$parent.getTextAlign();
        }
        return DEFAULT_FONT_SIZE_1.DEFAULT_TEXT_ALIGN;
    }
    isSVG() {
        const { api: { window: { SVGElement, SVGSVGElement }, }, } = this.$system;
        return (this.$element instanceof SVGElement &&
            !(this.$element instanceof SVGSVGElement));
    }
    isHTML() {
        const { api: { window: { HTMLElement }, }, } = this.$system;
        return this.$element instanceof HTMLElement;
    }
    isText() {
        const { api: { window: { Text }, }, } = this.$system;
        return this.$element instanceof Text;
    }
    isBase() {
        return this.$primitive && !this.$wrap && this.$root.length === 0;
    }
    getRootBase(path = []) {
        if (this.isBase()) {
            return [[path, this]];
        }
        let p = [];
        for (const subComponent of this.$root) {
            const subComponentId = this.getSubComponentId(subComponent);
            const subComponentRootBase = subComponent.getRootBase([
                ...path,
                subComponentId,
            ]);
            p = [...p, ...subComponentRootBase];
        }
        if (p.length === 0) {
            p = [[path, this]];
        }
        return p;
    }
    getBaseRoots() {
        if (this.isBase()) {
            return [this];
        }
        let p = [];
        for (const root of this.$root) {
            const rootRoots = root.getBaseRoots();
            p = [...p, ...rootRoots];
        }
        if (p.length === 0) {
            p = [this];
        }
        return p;
    }
    getFirstRootLeaf() {
        const base = this.getRootBase();
        const [_, leaf] = base[0];
        return leaf;
    }
    getBase(path = []) {
        if (this.isBase()) {
            return [[path, this]];
        }
        let p = [];
        const appendSubComponent = (subComponent) => {
            const subComponentId = this.getSubComponentId(subComponent);
            const subComponentRootBase = subComponent.getRootBase([
                ...path,
                subComponentId,
            ]);
            p = [...p, ...subComponentRootBase];
            for (const _subComponent of subComponent.$parentRoot) {
                appendSubComponent(_subComponent);
            }
        };
        for (const subComponent of this.$root) {
            appendSubComponent(subComponent);
        }
        return p;
    }
    pathGetSubComponent(path) {
        if (path.length == 0) {
            return this;
        }
        else {
            const [head, ...tail] = path;
            const subComponent = this.getSubComponent(head);
            return subComponent.pathGetSubComponent(tail);
        }
    }
    getRect() {
        return (0, getRect_1.getRect)(this.$node);
    }
    getBoundingClientRect() {
        const { api: { window: { Text }, }, } = this.$system;
        if (!this.$primitive) {
            throw new Error('cannot calculate position of multiple elements.');
        }
        if (this.$node instanceof Text) {
            // TODO
            return { x: 0, y: 0, width: 0, height: 0 };
        }
        const { $x, $y, $sx, $sy } = this.$context;
        const bcr = this.$node.getBoundingClientRect();
        const { x, y, width, height } = bcr;
        return {
            x: (x - $x) / $sx,
            y: (y - $y) / $sy,
            width: width / $sx,
            height: height / $sy,
        };
    }
    appendChild(child, slotName = 'default') {
        // console.log('Component', 'appendChild', slotName)
        const at = this.$children.length;
        this.memAppendChild(child, slotName, at);
        this.domAppendChild(child, slotName, at);
        this.postAppendChild(child, slotName, at);
        return at;
    }
    appendChildren(children, slotName) {
        this.memAppendChildren(children, slotName);
        this.domAppendChildren(children, slotName);
        this.postAppendChildren(children, slotName);
    }
    memAppendChildren(children, slotName) {
        for (const child of children) {
            this.memAppendChild(child, slotName, this.$children.length);
        }
    }
    postAppendChildren(children, slotName) {
        for (const child of children) {
            this.postAppendChild(child, slotName, this.$children.length);
        }
    }
    memAppendChild(child, slotName, at) {
        // console.log('Component', 'memAppendChild')
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        this.$slotChildren[slotName].push(child);
        this.$children.push(child);
        this.$childSlotName[at] = slotName;
        const slot = this.$slot[slotName];
        slot.memAppendParentChild(child, slotName, at, at);
    }
    domAppendChildren(children, slotName) {
        const fragment = new Component({}, this.$system);
        fragment.$element = new DocumentFragment();
        const slot = this.getLeafSlot(slotName);
        for (const child of children) {
            fragment._domAppendChild(child);
        }
        this._domAppendChild(fragment, slotName);
        for (const child of children) {
            child.$slotParent = slot;
        }
    }
    getLeafSlot(slotName) {
        let slot = this.$slot[slotName];
        while (slot.$slot[slotName] && slot.$slot[slotName] !== slot) {
            slot = slot.$slot[slotName];
        }
        return slot;
    }
    _domAppendChild(child, slotName = 'default', at, slot) {
        // console.log('Component', '_domAppendChild')
        slot = slot !== null && slot !== void 0 ? slot : this.getLeafSlot(slotName);
        at = at !== null && at !== void 0 ? at : this.$children.length;
        slot.domAppendParentChildAt(child, slotName, at, at);
    }
    domAppendChild(child, slotName = 'default', at) {
        // console.log('Component', '_domAppendChild')
        const slot = this.getLeafSlot(slotName);
        this._domAppendChild(child, slotName, at);
        child.$slotParent = slot;
    }
    postAppendChild(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.postAppendParentChild(child, slotName, at);
    }
    createSVGWrapper() {
        const { api: { document: { createElementNS }, }, } = this.$system;
        const element = createElementNS(namespaceURI_1.namespaceURI, 'svg');
        element.style.display = 'block';
        element.style.width = '100%';
        element.style.height = '100%';
        const component = new Component({}, this.$system, element);
        return component;
    }
    _createHTMLWrapper() {
        const { api: { document: { createElement }, }, } = this.$system;
        const element = createElement('div');
        element.style.width = '100%';
        element.style.height = '100%';
        const component = new Component({}, this.$system, element);
        return component;
    }
    templateChildWrapper(child, svg, html, fallback) {
        if (this.isHTML() && child.isSVG()) {
            return svg();
        }
        else if (this.isSVG() && child.isHTML()) {
            return html();
        }
        else {
            return fallback();
        }
    }
    domRemoveChild(child, slotName = 'default', at) {
        const slot = this.$slot[slotName];
        at = at !== null && at !== void 0 ? at : slot.$slotChildren[slotName].indexOf(child);
        slot.domRemoveParentChildAt(child, slotName, at, at, this.$slotParent);
        child.$slotParent = null;
    }
    postRemoveChild(child, at) {
        if (this.$mounted) {
            child.unmount();
        }
    }
    hasChild(at) {
        const has = this.$children.length > at;
        return has;
    }
    ascendChild(child, slotName = 'default') {
        this.removeChild(child);
        this.appendChild(child, slotName);
    }
    connect($unit, deep = true) {
        if (this.$connected) {
            // throw new Error('component is already connected')
            return;
        }
        this._connect($unit, deep);
        if (deep) {
            if (this.$unbundled) {
                for (const unitId in this.$subComponent) {
                    const childSubComponent = this.$subComponent[unitId];
                    const _ = (0, interface_1.getComponentInterface)(childSubComponent);
                    const subUnit = $unit.$refSubComponent({ unitId, _ });
                    childSubComponent.connect(subUnit);
                }
            }
        }
    }
    _connect($unit, deep = true) {
        // console.log(this.constructor.name, 'connect')
        if (this.$connected) {
            throw new Error('component is already connected');
            return;
        }
        this.$unit = (0, proxyWrap_1.proxyWrap)($unit, UCGEE_1.UCGEE);
        const listen = (event) => {
            this.$named_listener_count[event] = this.$named_listener_count[event] || 0;
            this.$named_listener_count[event]++;
            if (!this.$named_unlisten[event]) {
                this._addUnitEventListener(event);
            }
        };
        const unlisten = (event) => {
            this.$named_listener_count[event]--;
            if (this.$named_listener_count[event] === 0) {
                this._removeUnitEventListener(event);
            }
        };
        const handler = {
            unit: (moment) => {
                const { event: event_event, data: event_data } = moment;
                if (event_event === 'append_child') {
                    const bundle = event_data;
                    const at = this.$children.length;
                    const child = this._instanceChild({ bundle }, at);
                    const slot_name = 'default';
                    (0, array_1.push)(this.$remoteChildren, child);
                    this.appendChild(child, slot_name);
                }
                else if (event_event === 'append_children') {
                    const bundles = event_data;
                    const children = bundles.map((bundle, i) => this._instanceChild({ bundle }, this.$children.length + i));
                    const slot_name = 'default';
                    for (const child of children) {
                        (0, array_1.push)(this.$remoteChildren, child);
                    }
                    this.appendChildren(children, slot_name);
                }
                else if (event_event === 'remove_child') {
                    const at = event_data;
                    const child = this.$remoteChildren[at];
                    this.removeChildAt(at);
                    child.disconnect();
                    child.destroy();
                    (0, array_1.remove)(this.$remoteChildren, child);
                }
                else if (event_event === 'play') {
                    this.play();
                }
                else if (event_event === 'pause') {
                    this.pause();
                }
                else if (event_event === 'register') {
                    this.register(event_data);
                }
                else if (event_event === 'unregister') {
                    this.unregister();
                }
                else {
                    throw new Error('invalid event');
                }
            },
        };
        const unit_listener = (moment) => {
            const { type } = moment;
            handler[type] && handler[type](moment);
        };
        this.$named_listener_count = {};
        $unit.$getGlobalId({}, (remoteId) => {
            this.register(remoteId);
        });
        const all_unlisten = [];
        const events = [
            'append_child',
            'append_children',
            'remove_child',
            'register',
            'unregister',
            'play',
            'pause',
        ];
        const unit_unlisten = this.$unit.$watch({ events }, unit_listener);
        all_unlisten.push(unit_unlisten);
        this.$unit.$getSetup({}, (setup) => {
            const { events, animations, stopPropagation, stopImmediatePropagation, preventDefault, } = setup;
            for (const event of events) {
                if (makeEventListener_1.UI_EVENT_SET.has(event) || event.startsWith('_')) {
                    listen(event);
                }
            }
            for (const animation of animations) {
                const { keyframes, opt } = animation;
                this.animate(keyframes, opt);
            }
            for (const event of stopPropagation) {
                this.stopPropagation(event);
            }
        });
        const $emitter = $unit.$refEmitter({ _: ['EE'] });
        const unlisten_control = this.$control($emitter);
        all_unlisten.push(unlisten_control);
        const unlisten_emitter = (0, callAll_1.callAll)([
            $emitter.$addListener({ event: 'listen' }, ([{ event }]) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event) || event.startsWith('_')) {
                    listen(event);
                }
            }),
            $emitter.$addListener({ event: 'unlisten' }, ([{ event }]) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event || event.startsWith('_'))) {
                    unlisten(event);
                }
            }),
            $emitter.$addListener({ event: 'call' }, ([{ method, data }]) => {
                this._call(method, data);
            }),
        ]);
        all_unlisten.push(unlisten_emitter);
        this._unit_unlisten = (0, callAll_1.callAll)(all_unlisten);
        $unit.$children({}, ($children) => {
            if ($children.length > 0) {
                const children = $children.map(this._instanceChild);
                this.$remoteChildren = children;
                this.setChildren(children);
            }
        });
        this.$connected = true;
        this.onConnected($unit);
    }
    disconnect(deep = true) {
        this._disconnect();
        if (this.$unbundled) {
            for (const subUnitId in this.$subComponent) {
                const childSubComponent = this.$subComponent[subUnitId];
                childSubComponent.disconnect();
            }
        }
    }
    _disconnect(deep = true) {
        // console.log(this.constructor.name, 'disconnect')
        if (!this.$connected) {
            throw new Error('component is not already disconnected');
        }
        this._unit_unlisten();
        for (const child of this.$remoteChildren) {
            child.disconnect();
        }
        this.unregister();
        this.$connected = false;
        this.onDisconnected();
    }
    setChildren(children = [], slotNames = []) {
        const oldChildren = [...this.$children];
        for (const oldChild of oldChildren) {
            this.removeChild(oldChild);
        }
        for (let i = 0; i < children.length; i++) {
            const child = children[i];
            const slotName = slotNames[i];
            this.appendChild(child, slotName);
        }
    }
    memRemoveChildAt(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.memRemoveParentChildAt(child, slotName, at, at);
        (0, array_1.remove)(this.$slotChildren[slotName], child);
        (0, array_1.removeAt)(this.$children, at);
        (0, array_1.removeAt)(this.$childSlotName, at);
    }
    removeChildAt(at) {
        const child = this.$children[at];
        const slotName = this.$childSlotName[at];
        this.memRemoveChildAt(child, slotName, at);
        this.domRemoveChild(child, slotName, at);
        this.postRemoveChild(child, at);
        return at;
    }
    removeChild(child) {
        // console.log('Component', 'removeChild')
        const at = this.$children.indexOf(child);
        if (at > -1) {
            return this.removeChildAt(at);
        }
        else {
            throw new Error('child component not found');
        }
    }
    removeChildren() {
        const children = [...this.$children];
        for (const child of children) {
            this.removeChild(child);
        }
    }
    hasRoot(component) {
        return this.$root.includes(component);
    }
    pushRoot(component) {
        if (this.$root.indexOf(component) > -1) {
            throw new Error('component is already a root');
        }
        this.$root.push(component);
    }
    pullRoot(component) {
        const index = this.$root.indexOf(component);
        if (index > -1) {
            this.$root.splice(index, 1);
        }
        else {
            throw new Error('cannot pull; not root component');
        }
    }
    fitRoot(component, at) {
        this.$root.splice(at, 0, component);
    }
    registerRoot(component) {
        this.pushRoot(component);
        this.appendRoot(component);
    }
    registerRootAt(component, at) {
        (0, array_1.insert)(this.$root, component, at);
        this.insertRootAt(component, at);
    }
    unregisterRoot(component) {
        this.pullRoot(component);
        this.removeRoot(component);
    }
    memAppendRoot(component) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.push)(this.$mountRoot, component);
    }
    postAppendRoot(component) {
        (0, control_1._if)(this.$mounted, mount_1.mount, component, this._$context);
    }
    domAppendRoot(component, at) {
        (0, object_1.set)(component, '$slotParent', this);
        if (!this.$primitive) {
            if (!component.$primitive) {
                let i = 0;
                for (const root of component.$mountRoot) {
                    this.domAppendRoot(root, at + i);
                    i++;
                }
            }
            else {
                if (this.$slotParent) {
                    const i = at + this.$slotParent.$mountParentChildren.length - 1;
                    this.$slotParent.domAppendParentChildAt(component, 'default', i, i);
                }
                else {
                    this.domCommitAppendChild(component, this.$mountRoot.length - 1);
                }
            }
        }
        else {
            if (!component.$primitive) {
                let i = 0;
                for (const root of component.$mountRoot) {
                    this.domAppendRoot(root, i);
                    i++;
                }
            }
            else {
                this.domCommitAppendChild(component, this.$mountRoot.length - 1);
            }
        }
    }
    domCommitAppendChild(component, at) {
        this._domCommitChild__template(component, at, element_1.insertAt);
    }
    domCommitInsertChild(component, at) {
        this._domCommitChild__template(component, at, element_1.insertAt);
    }
    appendRoot(component) {
        // console.log('Component', 'appendRoot', component)
        const at = this.$root.length;
        this.memAppendRoot(component);
        this.domAppendRoot(component, at);
        this.postAppendRoot(component);
    }
    insertRootAt(component, _at) {
        this.memInsertRootAt(component, _at);
        this.domInsertRootAt(component, _at);
        this.postInsertRootAt(component, _at);
    }
    postInsertRootAt(component, _at) {
        this.$mounted && this.mountChild(component);
    }
    memInsertRootAt(component, _at) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.insert)(this.$mountRoot, component, _at);
    }
    domInsertRootAt(component, _at) {
        (0, object_1.set)(component, '$slotParent', this);
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    let i = 0;
                    if (this.$slotParent) {
                        this.$slotParent.domInsertParentChildAt(component, 'default', _at + i);
                    }
                    else {
                        this.domCommitInsertChild(root, _at);
                    }
                    i++;
                }
            }
            else {
                if (this.$slotParent) {
                    let p = this;
                    let index = this.$slotParent.$mountParentChildren.indexOf(p);
                    while (p && !this.$slotParent.$mountParentChildren.includes(p)) {
                        p = p.$parent;
                        index = this.$slotParent.$mountParentChildren.indexOf(p);
                    }
                    this.$slotParent.domInsertParentChildAt(component, 'default', index + _at);
                }
                else {
                    this.domCommitInsertChild(component, _at);
                }
            }
        }
        else {
            if (!component.$primitive) {
                let i = 0;
                for (const root of component.$mountRoot) {
                    this.domInsertRootAt(root, _at + i);
                    i++;
                }
            }
            else {
                this.domCommitInsertChild(component, _at);
            }
        }
    }
    appendAllRoot() {
        for (const root of this.$root) {
            this.appendRoot(root);
        }
    }
    appendAllMissingRoot() {
        for (const root of this.$root) {
            if (this.$mountRoot.includes(root)) {
                //
            }
            else {
                this.appendRoot(root);
            }
        }
    }
    removeAllRoot() {
        for (const root of [...this.$mountRoot]) {
            this.removeRoot(root);
        }
    }
    removeRoot(component) {
        const at = this.$root.indexOf(component);
        this.domRemoveRoot(component, at);
        this.memRemoveRoot(component);
        this.postRemoveRoot(component);
    }
    memRemoveRoot(component) {
        (0, array_1.pull)(this.$mountRoot, component);
    }
    domRemoveRoot(component, at) {
        (0, object_1.set)(component, '$slotParent', null);
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (let i = 0; i < component.$mountRoot.length; i++) {
                    if (this.$slotParent) {
                        this.$slotParent.domRemoveParentChildAt(component.$mountRoot[i], 'default', at, at, this.$slotParent);
                    }
                    else {
                        this.domRemoveRoot(component.$mountRoot[i], at);
                    }
                }
            }
            else {
                if (this.$slotParent && component.$slotParent) {
                    this.$slotParent.domRemoveParentChildAt(component, 'default', at, at, this.$slotParent);
                }
                else {
                    this.domCommitRemoveChild(component);
                }
            }
        }
        else {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    this.domRemoveRoot(root, at);
                }
            }
            else {
                this.domCommitRemoveChild(component);
            }
        }
    }
    postRemoveRoot(component) {
        (0, control_1._if)(this.$mounted, unmount_1.unmount, component);
    }
    compose() {
        for (const component of this.$root) {
            component.collapse();
            this.appendRoot(component);
        }
    }
    decompose(deep = true) {
        for (const component of [...this.$mountRoot]) {
            this.removeRoot(component);
            deep && component.uncollapse(deep, this.$slotParent);
        }
    }
    hasParentRoot(component) {
        return this.$parentRoot.includes(component);
    }
    pushParentRoot(component, slotName) {
        (0, array_1.push)(this.$parentRoot, component);
        (0, array_1.push)(this.$parentRootSlotName, slotName);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memPushParentChild(component, slotName);
    }
    insertParentRoot(component, at, slotName) {
        (0, array_1.insert)(this.$parentRoot, component, at);
        (0, array_1.insert)(this.$parentRootSlotName, slotName, at);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memInsertParentChild(component, at, slotName);
    }
    unshiftParentRoot(component, slotName) {
        (0, array_1.unshift)(this.$parentRoot, component);
        (0, array_1.unshift)(this.$parentRootSlotName, slotName);
    }
    pullParentRoot(component) {
        // console.log(this.constructor.name, 'pullParentRoot')
        const i = this.$parentRoot.indexOf(component);
        if (i > -1) {
            const slotName = this.$parentRootSlotName[i];
            (0, array_1.removeAt)(this.$parentRoot, i);
            (0, array_1.removeAt)(this.$parentRootSlotName, i);
            const slot = (0, object_1.get)(this.$slot, slotName);
            slot.memPullParentChild(component);
        }
        else {
            throw new Error('parent root not found');
        }
    }
    registerParentRoot(component, slotName = 'default') {
        this.pushParentRoot(component, slotName);
        this.appendParentRoot(component, slotName);
    }
    unregisterParentRoot(component) {
        // console.log('unregisgerParentRoot')
        this.removeParentRoot(component, this.$slotParent);
        this.pullParentRoot(component);
    }
    collapse(deep = true) {
        let i = 0;
        for (const component of this.$parentRoot) {
            const slotName = this.$parentRootSlotName[i];
            deep && component.collapse();
            this.appendParentRoot(component, slotName);
            i++;
        }
    }
    uncollapse(deep = true, slotParent) {
        for (const component of this.$parentRoot) {
            this.removeParentRoot(component, slotParent);
            deep && component.uncollapse(deep, slotParent !== null && slotParent !== void 0 ? slotParent : this.$slotParent);
        }
    }
    appendParentRoot(component, slotName) {
        // console.log(
        //   this.constructor.name,
        //   'appendParentRoot',
        //   component.constructor.name
        // )
        const at = this.$parentRoot.indexOf(component);
        this.memAppendParentRoot(component, slotName, at);
        this.domAppendParentRoot(component, slotName, at);
        this.postAppendParentRoot(component, slotName, at);
    }
    memAppendParentRoot(component, slotName, at) {
        // console.log(this.constructor.name, 'memAppendParentRoot', component.constructor.name, slotName, at)
        (0, array_1.push)(this.$mountParentRoot, component);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memAppendParentChild(component, 'default', at, at);
        component.$rootParent = this;
    }
    domAppendParentRoot(component, slotName, at) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.domAppendParentChildAt(component, 'default', at, at);
    }
    postAppendParentRoot(component, slotName, at) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.postAppendParentChild(component, slotName, at);
    }
    memPushParentChild(component, slotName) {
        (0, array_1.push)(this.$parentChildren, component);
        (0, array_1.push)(this.$parentChildrenSlot, slotName);
    }
    memInsertParentChild(component, at, slotName) {
        (0, array_1.insert)(this.$parentChildren, component, at);
        (0, array_1.insert)(this.$parentChildrenSlot, slotName, at);
    }
    memPullParentChild(component) {
        const at = this.$parentChildren.indexOf(component);
        (0, array_1.removeAt)(this.$parentChildren, at);
        (0, array_1.removeAt)(this.$parentChildrenSlot, at);
    }
    memAppendParentChild(component, slotName, at, _at) {
        (0, array_1.push)(this.$mountParentChildren, component);
    }
    domAppendParentChildAt(component, slotName, at, _at) {
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (let i = 0; i < component.$mountRoot.length; i++) {
                    this.domAppendParentChildAt(component.$mountRoot[i], slotName, at, at + i);
                }
            }
            else {
                if (this.$slotParent) {
                    this.$slotParent.domAppendParentChildAt(component, 'default', this.$slotParent.$mountParentChildren.length, this.$slotParent.$mountParentChildren.length);
                }
                else {
                    this.domCommitAppendChild(component, at);
                }
            }
        }
        else {
            if (!component.$primitive) {
                let j = 0;
                for (const root of component.$mountRoot) {
                    this.domAppendParentChildAt(root, slotName, at + j, at + j);
                    if (!root.$primitive) {
                        let i = 0;
                        for (const parentRoot of root.$mountParentChildren) {
                            this.domAppendParentChildAt(parentRoot, slotName, at, at);
                            i++;
                        }
                    }
                    j++;
                }
            }
            else {
                this.domCommitAppendChild(component, at);
            }
        }
        (0, object_1.set)(component, '$slotParent', this);
    }
    postAppendParentChild(component, slotName, at) {
        if (this.$mounted) {
            this.mountChild(component);
        }
    }
    postInsertParentChildAt(component, slotName, at) {
        if (this.$mounted) {
            this.mountChild(component);
        }
    }
    insertParentChildAt(component, slotName, i) {
        const slot = this.$slot[slotName];
        if (this === slot) {
            this.memInsertParentChildAt(component, slotName, i);
            this.domInsertParentChildAt(component, slotName, i);
            this.postInsertParentChildAt(component, slotName, i);
        }
        else {
            slot.insertParentChildAt(component, 'default', i);
        }
    }
    memInsertParentChildAt(component, slotName, i) {
        if (this.$mountRoot.indexOf(component) > -1) {
            throw new Error('root is already mounted');
        }
        (0, array_1.insert)(this.$mountParentChildren, component, i);
    }
    domInsertParentChildAt(component, slotName, i) {
        var _a, _b, _c;
        (0, object_1.set)(component, '$slotParent', this);
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    if (this.$slotParent) {
                        const index = ((_a = this.$parent) === null || _a === void 0 ? void 0 : _a.$root.includes(this))
                            ? (_b = this.$parent) === null || _b === void 0 ? void 0 : _b.$root.indexOf(this)
                            : 0;
                        this.$slotParent.domInsertParentChildAt(component, 'default', index + i);
                    }
                    else {
                        this.domCommitInsertChild(root, i);
                    }
                }
            }
            else {
                if (this.$slotParent) {
                    const index = ((_c = this.$parent) === null || _c === void 0 ? void 0 : _c.$root.includes(this))
                        ? this.$slotParent.$mountParentChildren.indexOf(this.$parent)
                        : this.$slotParent.$mountParentChildren.indexOf(this);
                    this.$slotParent.domInsertParentChildAt(component, 'default', index + i);
                }
                else {
                    this.domCommitInsertChild(component, i);
                }
            }
        }
        else {
            if (!component.$primitive) {
                let j = 0;
                for (const root of component.$root) {
                    this.domInsertParentChildAt(root, 'default', i + j);
                    j++;
                }
            }
            else {
                this.domCommitInsertChild(component, i);
            }
        }
    }
    removeParentChild(component, slotName = 'default', at) {
        const slot = this.$slot[slotName];
        if (slot === this) {
            const _at = this.$parentChildren.indexOf(component);
            this.domRemoveParentChildAt(component, slotName, at, _at, this.$slotParent);
            this.memRemoveParentChildAt(component, slotName, at, _at);
        }
        else {
            slot.removeParentChild(component, 'default', at);
        }
    }
    memRemoveParentChildAt(component, slotName, at, _at) {
        // console.log(this.constructor.name, 'memRemoveParentChildAt')
        const __at = this.$mountParentChildren.indexOf(component);
        (0, array_1.removeAt)(this.$mountParentChildren, __at);
    }
    domRemoveParentChildAt(component, slotName, at, _at, slotParent) {
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (let i = 0; i < component.$mountRoot.length; i++) {
                    if (this.$slotParent) {
                        this.$slotParent.domRemoveParentChildAt(component.$mountRoot[i], slotName, at, at, slotParent);
                    }
                    else {
                        this.domRemoveParentChildAt(component.$mountRoot[i], slotName, at, at, slotParent);
                    }
                }
            }
            else {
                if (this.$slotParent) {
                    this.$slotParent.domRemoveParentChildAt(component, 'default', at, at, slotParent);
                }
                else if (slotParent) {
                    slotParent.domCommitRemoveChild(component);
                }
                else {
                    this.domCommitRemoveChild(component);
                }
            }
        }
        else {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    this.domRemoveParentChildAt(root, slotName, at, at, slotParent);
                    if (!root.$primitive) {
                        let i = 1;
                        for (const parentRoot of root.$mountParentChildren) {
                            this.domRemoveParentChildAt(parentRoot, slotName, at, at, slotParent);
                        }
                    }
                }
            }
            else {
                this.domCommitRemoveChild(component);
            }
        }
        (0, object_1.set)(component, '$slotParent', null);
    }
    domCommitRemoveChild(component) {
        if (component.isParent()) {
            for (const root of component.$root) {
                this.domCommitRemoveChild(root);
            }
            return;
        }
        if (this.isParent()) {
            if (this.$slotParent && component.$slotParent) {
                this.$slotParent.domCommitRemoveChild(component);
            }
            else {
                if (this.$element.contains(component.$element)) {
                    (0, element_1.removeChild)(this.$element, component.$element);
                }
            }
        }
        else {
            if (this.$element.contains(component.$element)) {
                (0, element_1.removeChild)(this.$element, component.$element);
            }
        }
    }
    insertParentRootAt(component, at, slotName) {
        this.memInsertParentRootAt(component, at, slotName);
        this.domInsertParentRootAt(component, at, slotName);
        this.postInsertParentRootAt(component, at, slotName);
    }
    memInsertParentRootAt(component, i, slotName) {
        (0, array_1.insert)(this.$mountParentRoot, component, i);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.memInsertParentChildAt(component, slotName, i);
    }
    domInsertParentRootAt(component, i, slotName) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.domInsertParentChildAt(component, slotName, i);
    }
    postInsertParentRootAt(component, _at, slotName) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.postInsertParentChildAt(component, slotName, _at);
    }
    prependParentRoot(component, slotName) {
        this.insertParentRootAt(component, 0, slotName);
    }
    removeParentRoot(component, slotParent) {
        const at = this.$parentRoot.indexOf(component);
        const _at = this.$mountParentRoot.indexOf(component);
        const slotName = this.$parentRootSlotName[at];
        this.postRemoveParentRootAt(component, slotName, at, _at);
        this.domRemoveParentRootAt(component, slotName, at, _at, slotParent !== null && slotParent !== void 0 ? slotParent : this.$slotParent);
        this.memRemoveParentRootAt(component, slotName, at, _at);
    }
    memRemoveParentRoot(component, slotParent) {
        const at = this.$parentRoot.indexOf(component);
        const at_ = this.$mountParentRoot.indexOf(component);
        const slotName = this.$parentRootSlotName[at];
        this.memRemoveParentRootAt(component, slotName, at, at_);
    }
    memRemoveParentRootAt(component, slotName, at, _at) {
        (0, array_1.pull)(this.$mountParentRoot, component);
        const slot = this.$slot[slotName];
        slot.memRemoveParentChildAt(component, 'default', at, _at);
        slot.$rootParent = null;
    }
    domRemoveParentRootAt(component, slotName, at, _at, slotParent) {
        // console.log(this.constructor.name, 'domRemoveParentRootAt', component.constructor.name, slotName, at, _at)
        const slot = this.$slot[slotName];
        slot.domRemoveParentChildAt(component, 'default', at, _at, slotParent);
    }
    postRemoveParentRootAt(component, slotName, at, _at) {
        if (this.$mounted) {
            component.unmount();
        }
    }
    getSubComponent(id) {
        return this.$subComponent[id];
    }
    setControlled(controlled) {
        this.$controlled = controlled;
        if (this.$controlled) {
        }
    }
    setSubComponent(id, component) {
        (0, object_1.set)(component, '$parent', this);
        this.$subComponent[id] = component;
    }
    setSubComponents(component_map) {
        (0, object_1.forEachObjKV)(component_map, this.setSubComponent.bind(this));
    }
    removeSubComponent(id) {
        const component = this.$subComponent[id];
        (0, object_1.set)(component, '$parent', null);
        delete this.$subComponent[id];
        return component;
    }
    getSubComponentParentId(id) {
        const subComponent = this.getSubComponent(id);
        for (const parentSubComponentId in this.$subComponent) {
            const parentSubComponent = this.$subComponent[parentSubComponentId];
            if (parentSubComponent.$parentRoot.includes(subComponent)) {
                return parentSubComponentId;
            }
        }
        return null;
    }
    getSubComponentParent(id) {
        const subComponentParentId = this.getSubComponentParentId(id);
        if (subComponentParentId) {
            return this.getSubComponent(subComponentParentId);
        }
        return null;
    }
    getSlotSubComponentId(slotName) {
        const subComponentId = this.$slotId[slotName];
        return subComponentId;
    }
    getSubComponentId(subComponent) {
        for (const subComponentId in this.$subComponent) {
            const _subComponent = this.$subComponent[subComponentId];
            if (subComponent === _subComponent) {
                return subComponentId;
            }
        }
        return null;
    }
    getParentRootSlotId(subComponent) {
        const i = this.$parentRoot.indexOf(subComponent);
        if (i > -1) {
            const slotName = this.$parentRootSlotName[i];
            return slotName;
        }
        else {
            return null;
        }
    }
    getSlotPath(slotName) {
        const slotSubComponentId = this.getSlotSubComponentId(slotName);
        if (slotSubComponentId) {
            const slotSubComponent = this.getSubComponent(slotSubComponentId);
            const mappedSlotName = this.$slotTarget[slotName];
            const mappedSlotPath = slotSubComponent.getSlotPath(mappedSlotName);
            return [slotSubComponentId, ...mappedSlotPath];
        }
        else {
            return [];
        }
    }
    getPath() {
        let path = [];
        let c = this;
        let p = this.$parent;
        while (p) {
            const subComponentId = p.getSubComponentId(c);
            path.unshift(subComponentId);
            c = p;
            p = p.$parent;
        }
        return path;
    }
    _call(method, data = []) {
        if (this[method]) {
            this[method](...data);
        }
        else {
            throw 'method not implemented';
        }
    }
    reset() {
        //
    }
    play() {
        const animations = this.getAnimations();
        for (const animation of animations) {
            animation.play();
        }
    }
    pause() {
        const animations = this.getAnimations();
        for (const animation of animations) {
            animation.pause();
        }
    }
    destroy() {
        // console.log(this.constructor.name, 'destroy')
        this.onDestroy();
    }
}
exports.Component = Component;
//# sourceMappingURL=component.js.map