"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Component = void 0;
const NOOP_1 = require("../NOOP");
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 addListener_1 = require("./addListener");
const namespaceURI_1 = require("./component/namespaceURI");
const componentFromSpecId_1 = require("./componentFromSpecId");
const component_1 = require("./component_");
const context_1 = require("./context");
const DEFAULT_FONT_SIZE_1 = require("./DEFAULT_FONT_SIZE");
const makeEventListener_1 = require("./makeEventListener");
const mount_1 = require("./mount");
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 }) => {
    const { unit } = bundle;
    const { id, memory: { input = {} } = { input: {} } } = unit;
    const props = (0, object_1.mapObjVK)(input, ({ _data }) => {
        return _data;
    });
    const component = (0, componentFromSpecId_1.componentFromSpecId)(system, id, props);
    return component;
};
class Component {
    constructor($props, $system, $element) {
        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.$rootSVGWrapper = [];
        this.$parentChildrenSVGWrapper = [];
        this.$childrenSVGWrapper = [];
        this.$rootHTMLWrapper = [];
        this.$parentChildrenHTMLWrapper = [];
        this.$childrenHTMLWrapper = [];
        this.$mountRoot = [];
        this.$mountParentRoot = [];
        this.$mountParentChildren = [];
        this.$parent = null;
        this.$slotParent = null;
        this.$mounted = false;
        this.$listenCount = {};
        this.$transaction = false;
        this._prop_transformer = {};
        this._releasePointerCapture = {};
        this.$listener = [];
        this.$unlisten = [];
        this.$named_listener_count = {};
        this.$named_unlisten = {};
        this._addUnitEventListener = (event) => {
            const unlisten = this.addEventListener((0, makeEventListener_1.makeUIEventListener)(event, (data) => {
                this.$unit.$refEmitter({}).$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.component_)(component);
            const $childRef = this.$unit.$refChild({ at, _ });
            component.connect($childRef);
            return component;
        };
        this.addEventListener = (listener) => {
            return (0, addListener_1.addListener)(this, listener);
        };
        this.addEventListeners = (listeners) => {
            return (0, addListener_1.addListeners)(this, listeners);
        };
        this.$props = $props;
        this.$system = $system;
        this.$element = $element;
        this.$globalId = this.$system.registerComponent(this);
    }
    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);
    }
    refreshProp(prop) {
        this._onPropChanged(prop, this.$props[prop]);
    }
    onConnected($unit) { }
    onDisconnected() { }
    onRender() { }
    onMount() { }
    onUnmount($context) { }
    onDestroy() { }
    dispatchEvent(type, detail = {}, bubbles = true) {
        (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) {
        this.$element.addEventListener(event, function (event) {
            event.stopPropagation();
        }, { passive: true });
    }
    stopImmediatePropagation(event) {
        this.$element.addEventListener(event, function (event) {
            event.stopImmediatePropagation();
        }, { passive: true });
    }
    preventDefault(event) {
        this.$element.addEventListener(event, (event) => {
            event.preventDefault();
            return false;
        }, { passive: false });
    }
    hasPointerCapture(pointerId) {
        return !!this._releasePointerCapture[pointerId];
    }
    setPointerCapture(pointerId) {
        const { api: { input: { pointer: { setPointerCapture }, }, }, } = this.$system;
        // console.log(this.constructor.name, 'setPointerCapture', pointerId)
        if (this.$element instanceof Text) {
            throw new Error('Cannot set pointer capture on a text element.');
        }
        this._releasePointerCapture[pointerId] = setPointerCapture(this.$element, 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.$element instanceof HTMLElement) {
                this.$element.scrollIntoView(opt);
            }
        }
        else {
            this.$slot['default'].scrollIntoView(opt);
        }
    }
    mountDescendent(child) {
        child.mount(this.$context);
    }
    unmountDescendent(child) {
        return child.unmount();
    }
    _forAllMountDescendent(callback) {
        for (const subComponent of this.$mountRoot) {
            callback(subComponent);
        }
        for (const subComponent of this.$mountParentChildren) {
            callback(subComponent);
        }
        // for (const child of this.$parentChildren) {
        //   callback(child)
        // }
        // for (const child of this.$children) {
        //   callback(child)
        // }
    }
    mount($context) {
        // console.log(this.constructor.name, 'mount')
        if (this.$mounted) {
            console.warn('Cannot mount a mounted component.');
            // throw new Error('Cannot mount a mounted component.')
            return;
        }
        this.$context = $context;
        this.$mounted = true;
        this.onMount();
        this._forAllMountDescendent((child) => {
            this.mountDescendent(child);
        });
        this.onRender();
        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);
        }
    }
    unmount() {
        // console.log(this.constructor.name, 'unmount')
        if (!this.$mounted) {
            console.warn('Cannot unmount unmounted component');
            // throw new Error('Cannot unmount unmounted component')
        }
        const $context = this.$context;
        this._forAllMountDescendent((child) => {
            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;
            }
            this.$element.focus(options);
        }
        else {
            this.$slot['default'].focus(options);
        }
    }
    blur() {
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                return;
            }
            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;
        return containsSelection(this.$element);
    }
    animate(keyframes, options) {
        if (this.$slot['default'] === this) {
            if (this.$element instanceof Text) {
                // RETURN
                return;
            }
            return this.$element.animate(keyframes, options);
        }
        else {
            return this.$slot['default'].animate(keyframes, options);
        }
    }
    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,
        };
        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() {
        return (0, getSize_1.getSize)(this.$element);
    }
    getFontSize() {
        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() {
        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() {
        const 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() {
        const fontSize = (0, getTextAlign_1.getTextAlign)(this.$element);
        if (fontSize) {
            return fontSize;
        }
        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.$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];
        }
        return p;
    }
    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.$element);
    }
    getBoundingClientRect() {
        if (this.$context) {
            if (this.$element instanceof Text) {
                // RETURN
                return;
            }
            const { $x, $y, $sx, $sy } = this.$context;
            const bcr = this.$element.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, at) {
        // console.log('Component', '_domAppendChild')
        const slot = this.$slot[slotName];
        slot.domAppendParentChildAt(this.wrapChild(slot, child, at), slotName, at, at);
        child.$slotParent = slot;
    }
    postAppendChild(child, slotName, at) {
        child.$parent = this;
        const slot = this.$slot[slotName];
        slot.postAppendParentChild(child, slotName, at);
        // if (this.$mounted) {
        //   this.mountDescendent(child)
        // }
    }
    createSVGWrapper() {
        const { api: { document: { createElementNS }, }, } = this.$system;
        const element = createElementNS(namespaceURI_1.default, '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();
        }
    }
    ensureRootHTMLWrapper(at) {
        return (0, object_1.ensure)(this.$rootHTMLWrapper, at, this._createHTMLWrapper.bind(this));
    }
    ensureRootSVGWrapper(at) {
        return (0, object_1.ensure)(this.$rootSVGWrapper, at, this.createSVGWrapper.bind(this));
    }
    ensureParentChildHTMLWrapper(at) {
        return (0, object_1.ensure)(this.$parentChildrenHTMLWrapper, at, this._createHTMLWrapper.bind(this));
    }
    ensureParentChildSVGWrapper(at) {
        return (0, object_1.ensure)(this.$parentChildrenSVGWrapper, at, this.createSVGWrapper.bind(this));
    }
    ensureChildHTMLWrapper(at) {
        return (0, object_1.ensure)(this.$childrenHTMLWrapper, at, this._createHTMLWrapper.bind(this));
    }
    ensureChildSVGWrapper(at) {
        return (0, object_1.ensure)(this.$childrenSVGWrapper, at, this.createSVGWrapper.bind(this));
    }
    wrapChild(slot, child, at) {
        if (slot.isHMTL() && child.isSVG()) {
            const wrapper = slot.ensureChildSVGWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else if (slot.isSVG() && child.isHMTL()) {
            const wrapper = slot.ensureChildHTMLWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    wrapRoot(child, at) {
        if (this.isHMTL() && child.isSVG()) {
            // console.log('wrapRoot', at)
            const wrapper = this.ensureRootSVGWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else if (this.isSVG() && child.isHMTL()) {
            const wrapper = this.ensureRootHTMLWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    wrapParentChild(child, at) {
        if (this.isHMTL() && child.isSVG()) {
            const wrapper = this.ensureParentChildSVGWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else if (this.isSVG() && child.isHMTL()) {
            const wrapper = this.ensureParentChildHTMLWrapper(at);
            wrapper.appendChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    unwrapRoot(child, at) {
        if (this.isHMTL() && child.isSVG()) {
            const wrapper = this.$rootSVGWrapper[at];
            delete this.$rootSVGWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else if (this.isSVG() && child.isHMTL()) {
            const wrapper = this.$rootHTMLWrapper[at];
            delete this.$rootHTMLWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    unwrapParentChildren(child, at) {
        if (this.isHMTL() && child.isSVG()) {
            const wrapper = this.$parentChildrenSVGWrapper[at];
            delete this.$parentChildrenSVGWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else if (this.isSVG() && child.isHMTL()) {
            const wrapper = this.$parentChildrenHTMLWrapper[at];
            delete this.$parentChildrenHTMLWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    unwrapChild(slot, child, at) {
        if (slot.isHMTL() && child.isSVG()) {
            const wrapper = slot.$childrenSVGWrapper[at];
            delete slot.$childrenSVGWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else if (slot.isSVG() && child.isHMTL()) {
            const wrapper = slot.$childrenHTMLWrapper[at];
            delete slot.$childrenHTMLWrapper[at];
            wrapper.removeChild(child);
            return wrapper;
        }
        else {
            return child;
        }
    }
    domRemoveChild(child, slotName, at) {
        const slot = this.$slot[slotName];
        slot.domRemoveParentChildAt(this.unwrapChild(slot, child, at), slotName, at, at);
        child.$slotParent = null;
    }
    postRemoveChild(child, at) {
        child.$parent = null;
        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 (this.$unbundled) {
            for (const unitId in this.$subComponent) {
                const childSubComponent = this.$subComponent[unitId];
                const _ = (0, component_1.component_)(childSubComponent);
                const subUnit = this.$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')
            console.log('Component', 'connect called unnecessarily');
            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 at = this.$children.length;
                    const child = this._$instanceChild(event_data, at);
                    const slot_name = 'default';
                    this.appendChild(child, slot_name);
                }
                else if (event_event === 'remove_child') {
                    const { at } = event_data;
                    this.removeChildAt(at);
                }
            },
        };
        const unit_listener = (moment) => {
            const { type } = moment;
            handler[type] && handler[type](moment);
        };
        this.$named_listener_count = {};
        $unit.$getGlobalId({}, (remoteGlobalId) => {
            this.$remoteGlobalId = remoteGlobalId;
            this.$system.registerRemoteComponent(this.$globalId, remoteGlobalId);
        });
        const all_unlisten = [];
        const events = ['append_child', 'remove_child'];
        const unit_unlisten = this.$unit.$watch({ events }, unit_listener);
        all_unlisten.push(unit_unlisten);
        const $emitter = $unit.$refEmitter({});
        $emitter.$getEventNames({}, (events) => {
            for (const event of events) {
                if (makeEventListener_1.UI_EVENT_SET.has(event)) {
                    listen(event);
                }
            }
        });
        const unlisten_emitter = (0, callAll_1.callAll)([
            $emitter.$addListener({ event: 'listen' }, ({ event }) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event)) {
                    listen(event);
                }
            }),
            $emitter.$addListener({ event: 'unlisten' }, ({ event }) => {
                if (makeEventListener_1.UI_EVENT_SET.has(event)) {
                    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')
            console.warn('Component', 'disconnect called unnecessarily');
            return;
        }
        this._unit_unlisten();
        for (const child of this.$children) {
            child.disconnect();
        }
        this.$connected = false;
        this.onDisconnected();
    }
    memInsertChild(child, slotName, at) {
        // console.log('Component', 'memInsertChild')
        (0, array_1.insert)(this.$children, child, at);
        (0, array_1.insert)(this.$slotChildren[slotName], child, at);
        (0, array_1.insert)(this.$childSlotName, slotName, at);
    }
    domInsertChild(child, slotName, at) {
        // console.log('Component', 'domInsertChild')
        const slot = this.$slot[slotName];
        (0, element_1.insertBeforeAt)(slot.$element, child.$element, at);
    }
    postInsertChild(child, slotName, at) {
        // console.log('Component', 'postInsertChild')
        (0, object_1.set)(child, '$parent', this);
        this.$mounted && this.mountDescendent(child);
    }
    insertChild(child, at, slotName = 'default') {
        this.$slotChildren[slotName] = this.$slotChildren[slotName] || [];
        if (at > this.$slotChildren[slotName].length - 1) {
            this.appendChild(child, slotName);
        }
        else {
            this.memInsertChild(child, slotName, at);
            this.domInsertChild(child, slotName, at);
            this.postInsertChild(child, slotName, at);
        }
    }
    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');
        }
        (0, object_1.set)(component, '$parent', this);
        this.$root.push(component);
    }
    pullRoot(component) {
        const index = this.$root.indexOf(component);
        if (index > -1) {
            (0, object_1.set)(component, '$parent', null);
            this.$root.splice(index, 1);
        }
        else {
            throw new Error('Cannot pull; not root component');
        }
    }
    fitRoot(component, at) {
        this.$root.splice(at, 0, component);
    }
    prependRoot(component) {
        (0, array_1.unshift)(this.$root, component);
        (0, element_1.prepend)(this.$element, component.$element);
        (0, control_1._if)(this.$mounted, mount_1.mount, component, this.$context);
    }
    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();
        }
        (0, array_1.push)(this.$mountRoot, component);
        (0, object_1.set)(component, '$slotParent', this);
    }
    postAppendRoot(component) {
        (0, object_1.set)(component, '$parent', this);
        (0, control_1._if)(this.$mounted, mount_1.mount, component, this.$context);
    }
    domAppendRoot(component) {
        const wrapped = this.wrapRoot(component, this.$mountRoot.length - 1);
        (0, element_1.appendChild)(this.$element, wrapped.$element);
    }
    appendRoot(component) {
        // console.log('Component', 'appendRoot', component)
        const at = this.$mountRoot.length;
        this.memAppendRoot(component);
        this.domAppendRoot(component);
        this.postAppendRoot(component);
    }
    insertRootAt(component, _at) {
        (0, array_1.insert)(this.$mountRoot, component, _at);
        const nextRoot = (0, array_1.at)(this.$root, _at + 1);
        (0, element_1.insertBefore)(this.$element, component.$element, nextRoot.$element);
        (0, object_1.set)(component, '$parent', this);
        this.$mounted && this.mountDescendent(component);
    }
    appendAllRoot() {
        for (const root of this.$root) {
            this.appendRoot(root);
        }
    }
    removeAllRoot() {
        for (const root of this.$root) {
            this.removeRoot(root);
        }
    }
    removeRoot(component) {
        const at = this.$root.indexOf(component);
        this.memRemoveRoot(component);
        this.domRemoveRoot(component, at);
        this.postRemoveRoot(component);
    }
    memRemoveRoot(component) {
        (0, array_1.pull)(this.$mountRoot, component);
        (0, object_1.set)(component, '$slotParent', null);
    }
    domRemoveRoot(component, at) {
        const wrapped = this.unwrapRoot(component, at);
        (0, element_1.removeChild)(this.$element, wrapped.$element);
    }
    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() {
        for (const component of this.$root) {
            this.removeRoot(component);
            component.uncollapse();
        }
    }
    startTransaction() {
        const unlisten = this._startTransaction();
        const allUnlisten = [unlisten];
        for (const subComponentId in this.$subComponent) {
            const subComponent = this.$subComponent[subComponentId];
            const subComponentUnlisten = subComponent.startTransaction();
            allUnlisten.push(subComponentUnlisten);
        }
        return (0, callAll_1.callAll)(allUnlisten);
    }
    _startTransaction() {
        this.$transaction = true;
        return () => {
            this.$transaction = false;
        };
    }
    hasParentRoot(component) {
        return this.$parentRoot.includes(component);
    }
    pushParentRoot(component, slotName) {
        // console.log(this.constructor.name, 'pushParentRoot', component.constructor.name, slotName)
        if (this.$parentRoot.indexOf(component) > -1) {
            throw new Error('');
        }
        (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) {
            (0, array_1.removeAt)(this.$parentRoot, i);
            const [slotName] = (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.pullParentRoot(component);
    }
    collapse() {
        let i = 0;
        for (const component of this.$parentRoot) {
            const slotName = this.$parentRootSlotName[i];
            component.collapse();
            this.appendParentRoot(component, slotName);
            i++;
        }
    }
    uncollapse() {
        for (const component of this.$parentRoot) {
            this.removeParentRoot(component);
            component.uncollapse();
        }
    }
    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)
        if (this.$mountParentRoot.indexOf(component) > -1) {
            throw new Error();
        }
        (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);
        (0, object_1.set)(component, '$parent', this);
    }
    memPushParentChild(component, slotName) {
        // console.log(
        //   this.constructor.name,
        //   'memPushParentChild',
        //   component.constructor.name
        // )
        (0, array_1.push)(this.$parentChildren, component);
        (0, array_1.push)(this.$parentChildrenSlot, slotName);
        // set(component, '$slotParent', this)
    }
    memPullParentChild(component, _at) {
        (0, array_1.removeAt)(this.$parentChildren, _at);
        (0, array_1.removeAt)(this.$parentChildrenSlot, _at);
        // set(component, '$slotParent', null)
    }
    memAppendParentChild(component, slotName, at, _at) {
        (0, array_1.push)(this.$mountParentChildren, component);
    }
    domAppendParentChildAt(component, slotName, at, _at) {
        const wrapped = this.wrapParentChild(component, at);
        (0, element_1.appendChild)(this.$element, wrapped.$element);
        (0, object_1.set)(component, '$slotParent', this);
    }
    postAppendParentChild(component, slotName, at) {
        if (this.$mounted) {
            this.mountDescendent(component);
        }
    }
    postInsertParentChildAt(component, slotName, at) {
        if (this.$mounted) {
            this.mountDescendent(component);
        }
    }
    insertParentChildAt(component, slotName, at, _at) {
        const slot = this.$slot[slotName];
        if (this === slot) {
            this.memInsertParentChildAt(component, slotName, at, _at);
            this.domInsertParentChildAt(component, slotName, at, _at);
        }
        else {
            slot.insertParentChildAt(component, 'default', at, _at);
        }
    }
    memInsertParentChildAt(component, slotName, at, _at) {
        (0, array_1.insert)(this.$parentChildren, component, _at);
        (0, array_1.insert)(this.$parentChildrenSlot, slotName, _at);
        (0, object_1.set)(component, '$slotParent', this);
    }
    domInsertParentChildAt(component, slotName, at, _at) {
        const slot = this.$slot[slotName];
        const nextElement = slot.$element.childNodes[_at];
        (0, element_1.insertBefore)(slot.$element, component.$element, nextElement);
    }
    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.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) {
        const wrapped = this.wrapParentChild(component, at);
        const _slotName = this.$parentChildrenSlot[_at];
        const slot = this.$slot[_slotName] || this;
        (0, element_1.removeChild)(slot.$element, wrapped.$element);
        (0, object_1.set)(component, '$slotParent', null);
    }
    insertParentRootAt(component, at, slotName) {
        this.memInsertParentRootAt(component, at, slotName);
        this.domInsertParentRootAt(component, at, slotName);
        this.postInsertParentRootAt(component, at, slotName);
    }
    memInsertParentRootAt(component, _at, slotName) {
        (0, array_1.insert)(this.$mountParentRoot, component, _at);
        const slot = (0, object_1.get)(this.$slot, slotName);
        const at = this.$parentRoot.indexOf(component);
        slot.memInsertParentChildAt(component, slotName, at, _at);
    }
    domInsertParentRootAt(component, _at, slotName) {
        const slot = (0, object_1.get)(this.$slot, slotName);
        const at = this.$parentRoot.indexOf(component);
        slot.insertParentChildAt(component, slotName, at, _at);
    }
    postInsertParentRootAt(component, _at, slotName) {
        (0, object_1.set)(component, '$parent', this);
        const slot = (0, object_1.get)(this.$slot, slotName);
        slot.postInsertParentChildAt(component, slotName, _at);
    }
    prependParentRoot(component, slotName) {
        this.insertParentRootAt(component, 0, slotName);
    }
    removeParentRoot(component) {
        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);
        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) {
        // console.log(this.constructor.name, 'domRemoveParentRootAt', component.constructor.name, slotName, at, _at)
        const slot = this.$slot[slotName];
        slot.domRemoveParentChildAt(component, 'default', at, _at);
    }
    postRemoveParentRootAt(component, slotName, at, _at) {
        component.$parent = null;
        if (this.$mounted) {
            component.unmount();
        }
    }
    getSubComponent(id) {
        return this.$subComponent[id];
    }
    setSubComponent(id, component) {
        this.$subComponent[id] = component;
    }
    removeSubComponent(id) {
        const component = this.$subComponent[id];
        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 [];
        }
    }
    _call(method, data = []) {
        if (this[method]) {
            this[method](...data);
        }
        else {
            throw 'method not implemented';
        }
    }
    destroy() {
        this.onDestroy();
    }
}
exports.Component = Component;
//# sourceMappingURL=component.js.map