"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Component = void 0;
const NOOP_1 = require("../NOOP");
const ANIMATION_PROPERTY_DELTA_PAIRS_1 = require("../system/platform/component/app/Editor/ANIMATION_PROPERTY_DELTA_PAIRS");
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 addListener_1 = require("./addListener");
const animateSimulate_1 = require("./animation/animateSimulate");
const namespaceURI_1 = require("./component/namespaceURI");
const componentFromSpecId_1 = require("./componentFromSpecId");
const component_1 = require("./component_");
const context_1 = require("./context");
const custom_1 = require("./event/custom");
const drag_1 = require("./event/drag");
const extractTrait_1 = require("./extractTrait");
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 {
    constructor($props, $system, $element, $node) {
        this.$_ = [];
        this.$changed = new Set();
        this.$ref = {};
        this.$connected = false;
        this.$primitive = true;
        this.$propUnlisten = {};
        // AD HOC
        // Avoid recursively connecting "JavaScript Defined Components"
        this.$unbundled = true;
        this.$children = [];
        this.$slotChildren = { default: [] };
        this.$childSlotName = [];
        this.$slot = { default: this };
        this.$slotId = {};
        this.$slotTarget = {};
        this.$subComponent = {};
        this.$root = [];
        this.$wrap = false;
        this.$parentRoot = [];
        this.$parentRootSlotName = [];
        this.$parentChildren = [];
        this.$parentChildrenSlot = [];
        this.$mountRoot = [];
        this.$mountParentRoot = [];
        this.$mountParentChildren = [];
        this.$parent = null;
        this.$slotParent = null;
        this.$mounted = false;
        this.$listenCount = {};
        this._prop_transformer = {};
        this._releasePointerCapture = {};
        this.$detached = false;
        this._animateBase = (base, hostSlot, reverse = false, commit) => {
            var _a, _b;
            const { foreground: { html }, api: { text: { measureText }, document: { createElement }, layout: { reflectChildrenTrait }, }, } = this.$system;
            const allAbort = [];
            let hostTarget = hostSlot;
            while ((_b = (_a = hostTarget.$element) === null || _a === void 0 ? void 0 : _a.classList) === null || _b === void 0 ? void 0 : _b.contains('__parent')) {
                hostTarget = hostTarget.$slotParent;
            }
            const hostTrait = (0, extractTrait_1.extractTrait)(hostTarget, measureText);
            const hostStyle = (0, rawExtractStyle_1.rawExtractStyle)(hostTarget.$element);
            delete hostStyle['transform'];
            delete hostStyle['opacity'];
            const baseStyle = (0, reflectComponentBaseTrait_1.getBaseStyle)(base, [], (leafPath, leafComp) => {
                return (0, rawExtractStyle_1.rawExtractStyle)(leafComp.$element);
            });
            const targetTraits = reflectChildrenTrait(hostTrait, hostStyle, baseStyle, []);
            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()}`,
                });
                hostSlot.$element.appendChild(leafFrame);
                !reverse && this.domRemoveLeaf(leaf);
                leafComp.unmount();
                leafFrame.appendChild(leafComp.$element);
                leafComp.$slotParent = hostSlot;
                leafComp.mount(hostSlot.$context);
                leafFrames.push(leafFrame);
                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 }) => {
                    leafFrame.style.left = `${x -
                        (reverse ? this.$context.$x : hostTrait.x) +
                        ((Math.abs(sx) - 1) * width) / 2}px`;
                    leafFrame.style.top = `${y -
                        (reverse ? this.$context.$y : hostTrait.y) +
                        ((Math.abs(sy) - 1) * height) / 2}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);
                            hostSlot.$element.removeChild(leafFrame);
                        }
                        commit();
                    }
                });
                allAbort.push(abortAnimation);
            }
            return (0, callAll_1.callAll)(allAbort);
        };
        this.templateBase = (base, root, parentRoot) => {
            // console.log('Component', 'templateBase', sub_component_id)
            for (const leaf of base) {
                this.templateLeaf(leaf, root, parentRoot);
            }
        };
        this.templateLeaf = (leaf, root, parentRoot) => {
            // console.log('Component', 'templateLeaf', sub_component_id)
            const [leaf_path, leaf_comp] = leaf;
            const leaf_parent_last = leaf_path[leaf_path.length - 1];
            const leaf_parent_path = leaf_path.slice(0, -1);
            const leaf_parent = this.pathGetSubComponent(leaf_parent_path);
            if (leaf_parent === leaf_comp) {
                //
            }
            else {
                const parent_id = leaf_parent.getSubComponentParentId(leaf_parent_last);
                if (parent_id) {
                    const parent = leaf_parent.getSubComponent(parent_id);
                    parentRoot(parent, leaf_comp);
                }
                else {
                    root(leaf_parent, leaf_comp);
                }
            }
        };
        this.domRemoveBase = (base) => {
            this.templateBase(base, (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.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) => {
            // console.log('Component', 'domAppendBase', sub_component_id)
            this.templateBase(base, (parent, leaf_comp) => {
                parent.domAppendRoot(leaf_comp);
            }, (parent, leaf_comp) => {
                const index = parent.$parentRoot.indexOf(leaf_comp);
                parent.domAppendParentRoot(leaf_comp, '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, component_1.getComponentInterface)(component);
            const $childRef = this.$unit.$refChild({ at, _ });
            component.connect($childRef);
            return component;
        };
        this.addEventListener = (listener) => {
            if (this.$primitive) {
                return (0, addListener_1.addListener)(this, listener);
            }
            else {
                const allRootUnlisten = [];
                for (const root of this.$root) {
                    const rootUnlisten = root.addEventListener(listener);
                    allRootUnlisten.push(rootUnlisten);
                }
                return (0, callAll_1.callAll)(allRootUnlisten);
            }
        };
        this.addEventListeners = (listeners) => {
            if (this.$primitive) {
                return (0, addListener_1.addListeners)(this, listeners);
            }
            else {
                const allRootUnlisten = [];
                for (const root of this.$root) {
                    const rootUnlisten = root.addEventListeners(listeners);
                    allRootUnlisten.push(rootUnlisten);
                }
                return (0, callAll_1.callAll)(allRootUnlisten);
            }
        };
        this.$props = $props;
        this.$system = $system;
        this.$element = $element;
        this._$node = $node !== null && $node !== void 0 ? $node : $element;
        this.register();
    }
    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) {
        if (this.$primitive) {
            (0, context_1.dispatchCustomEvent)(this.$element, type, detail, bubbles);
        }
        else {
            if (this.$mounted) {
                (0, context_1.dispatchCustomEvent)(this.$slotParent.$element, type, detail, bubbles);
            }
            else {
                (0, context_1.dispatchCustomEvent)(this.$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);
        const base = this.getRootBase();
        for (const [_, leaf] of base) {
            leaf.$element.addEventListener(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) {
        const base = this.getRootBase();
        for (const [_, leaf] of base) {
            leaf.$element.addEventListener('dragstart', (event) => {
                event.dataTransfer.setData(type, text);
            });
        }
    }
    attachDropTarget() {
        const rootLeaf = this.getFirstRootLeaf();
        rootLeaf.$element.addEventListener('dragover', (event) => {
            event.preventDefault();
        });
        rootLeaf.$element.addEventListener('drop', (event) => __awaiter(this, void 0, void 0, function* () {
            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 = yield 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._preventDefaultSet = (_a = this._preventDefaultSet) !== null && _a !== void 0 ? _a : new Set();
        if (this._preventDefaultSet.has(event)) {
            return;
        }
        this._preventDefaultSet.add(event);
        const listener = (event) => {
            event.preventDefault();
            return false;
        };
        const opt = { passive: false };
        this.$element.addEventListener(event, listener, opt);
        return () => {
            // @ts-ignore
            this.$element.removeEventListener(event, listener, opt);
        };
    }
    hasPointerCapture(pointerId) {
        return !!this._releasePointerCapture[pointerId];
    }
    setPointerCapture(pointerId) {
        const { api: { 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')
        }
    }
    scrollIntoView(opt) {
        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 firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.togglePopover) {
                firstLeaf.$element.togglePopover();
            }
        }
        else {
            //
        }
    }
    hidePopover() {
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.hidePopover) {
                firstLeaf.$element.hidePopover();
            }
        }
        else {
            //
        }
    }
    showPopover() {
        const firstLeaf = this.getFirstRootLeaf();
        if (firstLeaf.$element instanceof HTMLElement) {
            if (firstLeaf.$element.showPopover) {
                firstLeaf.$element.showPopover();
            }
        }
        else {
            //
        }
    }
    register() {
        const { registerLocalComponent } = this.$system;
        if (this.$localId) {
            return;
        }
        if (!this.$remoteId) {
            return;
        }
        this.$localId = registerLocalComponent(this, this.$remoteId);
        this._forAllDescendent((child) => {
            child.register();
        });
    }
    unregister() {
        const { unregisterLocalComponent } = this.$system;
        if (!this.$localId) {
            return;
        }
        unregisterLocalComponent(this.$remoteId, this.$localId);
        this.$localId = undefined;
    }
    detach(host, opt) {
        // console.log('Component', 'detach', host, opt)
        const { getLocalComponents } = this.$system;
        const { animate = false } = opt;
        if (this.$detached) {
            throw new Error('component is already detached');
        }
        this.$detached = true;
        const hostGlobalId = host.replace('unit://', '');
        const hosts = getLocalComponents(hostGlobalId);
        if (hosts.length === 0) {
            // TODO maybe the host has not been registered yet
            return;
        }
        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 (animate) {
            this._animateBase(base, hostSlot, false, commit);
        }
        else {
            this.domRemoveBase(base);
            commit();
        }
    }
    attach(opt) {
        // console.log('Component', 'detach', opt)
        if (!this.$detached) {
            throw new Error('component is not detached');
        }
        const { animate } = opt;
        this.$detached = false;
        const base = this.getRootBase();
        let leafEnd = 0;
        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.$slotParent;
                this._animateBase([leaf], targetSlot, true, () => {
                    leafEnd++;
                    leafComp.unmount();
                    this.domAppendBase([leaf]);
                    leafComp.mount(this.$context);
                });
            }
        }
        else {
            this.domAppendBase(base);
        }
    }
    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);
        }
        if (this.$listenCount['mount']) {
            this.dispatchEvent('mount', {}, false);
        }
    }
    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();
        }
    }
    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.$listenCount['unmount']) {
            this.dispatchEvent('unmount', {}, false);
        }
    }
    focus(options = { preventScroll: true }) {
        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() {
        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 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() {
        let offset = this;
        // while (
        //   offset &&
        //   (!(offset.$element instanceof HTMLElement) ||
        //     offset.$element.style.position === '' ||
        //     offset.$element.style.position === 'contents') &&
        //   offset.$element instanceof HTMLElement &&
        //   offset.$element.style.display !== 'flex'
        // ) {
        //   offset = offset.$slotParent
        // }
        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() {
        if (!this.$mounted) {
            throw new Error('cannot calculate position of unmounted component');
        }
        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);
    }
    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;
    }
    getRootPosition(rootId) {
        // TODO
        return geometry_1.NULL_VECTOR;
    }
    getParentRootPosition(subComponentId, parentRootId) {
        // TODO
        return geometry_1.NULL_VECTOR;
    }
    isSVG() {
        return (this.$element instanceof SVGElement &&
            !(this.$element instanceof SVGSVGElement));
    }
    isHMTL() {
        return this.$element instanceof HTMLElement;
    }
    isText() {
        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;
    }
    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() {
        if (this.$context) {
            if (!this.$primitive) {
                throw new Error('cannot calculate position of multiple elements.');
            }
            if (this.$node instanceof Text) {
                // TODO
                return;
            }
            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,
            };
        }
        else {
            throw new Error('component not mounted');
        }
    }
    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;
    }
    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);
    }
    domAppendChild(child, slotName = 'default', at) {
        // console.log('Component', '_domAppendChild')
        at = at !== null && at !== void 0 ? at : this.$children.length;
        // BUG
        let slot = this.$slot[slotName];
        while (slot.$slot[slotName] && slot.$slot[slotName] !== slot) {
            slot = slot.$slot[slotName];
        }
        slot.domAppendParentChildAt(child, slotName, at, 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.isHMTL() && child.isSVG()) {
            return svg();
        }
        else if (this.isSVG() && child.isHMTL()) {
            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, component_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 = $unit;
        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';
                    this.appendChild(child, slot_name);
                }
                else if (event_event === 'remove_child') {
                    const at = event_data;
                    const child = this.$children[at];
                    this.removeChildAt(at);
                    child.destroy();
                }
                else if (event_event === 'register') {
                    this.register();
                }
                else if (event_event === 'unregister') {
                    this.unregister();
                }
                else if (event_event === 'play') {
                    this.play();
                }
                else {
                    this.pause();
                }
            },
        };
        const unit_listener = (moment) => {
            const { type } = moment;
            handler[type] && handler[type](moment);
        };
        this.$named_listener_count = {};
        $unit.$getGlobalId({}, (remoteId) => {
            this.$remoteId = remoteId;
            this.register();
        });
        $unit.$getAnimations({}, (animations) => {
            for (const animation of animations) {
                const { keyframes, opt } = animation;
                this.animate(keyframes, opt);
            }
        });
        const all_unlisten = [];
        const events = [
            'append_child',
            '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_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.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.$children) {
            child.disconnect();
        }
        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) {
        (0, object_1.set)(component, '$slotParent', this);
        if (!this.$primitive) {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    this.domAppendRoot(root);
                }
            }
            else {
                if (this.$slotParent) {
                    this.$slotParent.domAppendParentChildAt(component, 'default', this.$slotParent.$mountParentChildren.length, this.$slotParent.$mountParentChildren.length);
                }
                else {
                    this.domCommitAppendChild(component);
                }
            }
        }
        else {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    this.domAppendRoot(root);
                }
            }
            else {
                this.domCommitAppendChild(component);
            }
        }
    }
    domCommitAppendChild(component) {
        (0, element_1.appendChild)(this.$element, component.$element);
        // set(component, '$slotParent', this)
    }
    domCommitInsertChild(component, at) {
        (0, element_1.insertAt)(this.$element, component.$element, at);
        // set(component, '$slotParent', this)
    }
    appendRoot(component) {
        // console.log('Component', 'appendRoot', component)
        this.memAppendRoot(component);
        this.domAppendRoot(component);
        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) {
        var _a, _b;
        (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)
                            : this.$slotParent.$mountParentChildren.indexOf(this);
                        this.$slotParent.domInsertParentChildAt(component, 'default', index + _at);
                    }
                    else {
                        this.domCommitInsertChild(root, _at);
                    }
                }
            }
            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) {
                for (const root of component.$mountRoot) {
                    this.domInsertRootAt(root, _at);
                }
            }
            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);
                    }
                    (0, element_1.appendChild)(component.$element, component.$mountRoot[i].$element);
                }
            }
            else {
                if (this.$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);
                    (0, element_1.appendChild)(component.$element, root.$element);
                }
            }
            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);
    }
    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, i);
        }
        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);
    }
    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);
    }
    memPullParentChild(component, _at) {
        (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);
                }
            }
        }
        else {
            if (!component.$primitive) {
                for (const root of component.$mountRoot) {
                    this.domAppendParentChildAt(root, slotName, at, at);
                    if (!root.$primitive) {
                        let i = 1;
                        for (const parentRoot of root.$mountParentChildren) {
                            this.domAppendParentChildAt(parentRoot, slotName, at + i, at + i);
                            i++;
                        }
                    }
                }
            }
            else {
                this.domCommitAppendChild(component);
            }
        }
        (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.$mountRoot) {
                    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);
                    }
                    (0, element_1.appendChild)(component.$element, component.$mountRoot[i].$element);
                }
            }
            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);
                    (0, element_1.appendChild)(component.$element, root.$element);
                    if (!root.$primitive) {
                        let i = 1;
                        for (const parentRoot of root.$mountParentChildren) {
                            this.domRemoveParentChildAt(parentRoot, slotName, at, at, slotParent);
                        }
                    }
                }
            }
            else {
                if (this.$element.contains(component.$element)) {
                    this.domCommitRemoveChild(component);
                }
                else {
                    component.$slotParent.domRemoveChild(component);
                }
            }
        }
        (0, object_1.set)(component, '$slotParent', null);
    }
    domCommitRemoveChild(component) {
        (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);
    }
    memRemoveParentRootAt(component, slotName, at, _at) {
        (0, array_1.pull)(this.$mountParentRoot, component);
        const slot = this.$slot[slotName];
        slot.memRemoveParentChildAt(component, 'default', at, _at);
    }
    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];
    }
    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;
    }
    getSlotSubComponentId(slotName) {
        const subComponentId = this.$slotId[slotName];
        return subComponentId;
        // const slot = this.$slot[slotName] || this
        // if (slot === this) {
        //   return null
        // } else {
        //   return this.getSubComponentId(slot)
        // }
    }
    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';
        }
    }
    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