/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
// #region Imports
// #endregion
export class CustomAttributeRegistry {
    // #region Fields
    _attrMap;
    _elementMap;
    _observer;
    // #endregion
    // #region Ctor
    constructor(ownerDocument) {
        this.ownerDocument = ownerDocument;
        this._attrMap = new Map();
        this._elementMap = new WeakMap();
        this._observer = new MutationObserver((mutations) => {
            mutations.forEach((m) => {
                if (m.type === 'attributes') {
                    if (m.attributeName) {
                        const attr = this.tryGetConstructor(m.attributeName);
                        if (attr) {
                            if (m.target instanceof Element) {
                                this.handleChange(m.attributeName, m.target, m.oldValue);
                            }
                        }
                    }
                }
                else {
                    Array.from(m.removedNodes).forEach((x) => {
                        if (x instanceof Element) {
                            this.elementDisconnected(x);
                        }
                    });
                    Array.from(m.addedNodes).forEach((x) => {
                        if (x instanceof Element) {
                            this.elementConnected(x);
                        }
                    });
                }
            });
        });
    }
    // #endregion
    // #region Properties
    ownerDocument;
    // #endregion
    // #region Methods
    define(attrName, ctor) {
        this._attrMap.set(attrName, ctor);
        this.upgradeAttr(attrName);
        this.reobserve();
    }
    has(attrName) {
        // This._attrMap.get(attrName);
        return this._attrMap.has(attrName);
    }
    get(element, attrName) {
        const map = this._elementMap.get(element);
        if (!map) {
            return undefined;
        }
        return map.get(attrName);
    }
    tryGetConstructor(attrName) {
        return this._attrMap.get(attrName);
    }
    observe() {
        this._observer.observe(this.ownerDocument, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeOldValue: true,
            attributeFilter: Array.from(this._attrMap.keys())
            // AttributeFilter: [...this._attrMap.keys()], // Broken in Oculus
            // AttributeFilter: this._attrMap.keys(), // This works in Chrome, but TS complains, and not clear if it should work in all browsers yet: https://github.com/whatwg/dom/issues/1092
        });
    }
    unobserve() {
        this._observer.disconnect();
    }
    reobserve() {
        this.unobserve();
        this.observe();
    }
    upgradeAttr(attrName, node = this.ownerDocument) {
        const matches = node.querySelectorAll(`[${attrName}]`);
        // Possibly create custom attributes that may be in the given 'node' tree.
        // Use a forEach as Edge doesn't support for...of on a NodeList
        // Array.prototype.forEach.call(matches, (element: Element) => this.handleChange(attrName, element, null));
        Array.from(matches).forEach((element) => {
            this.handleChange(attrName, element, null);
        });
    }
    elementConnected(element) {
        if (element.nodeType !== 1) {
            return;
        }
        // For each of the connected element's attribute, possibly instantiate the custom attributes.
        // Use a forEach as Safari 10 doesn't support for...of on NamedNodeMap (attributes)
        // Array.prototype.forEach.call(element.attributes, (attr: Attr) => {
        //     If (this.getConstructor(attr.name)) {
        //         This.handleChange(attr.name, element, null);
        //     }
        // });
        Array.from(element.attributes).forEach((attr) => {
            if (this.tryGetConstructor(attr.name)) {
                this.handleChange(attr.name, element, null);
            }
        });
        // Possibly instantiate custom attributes that may be in the subtree of the connected element.
        this._attrMap.forEach((_constructor, attr) => {
            this.upgradeAttr(attr, element);
        });
    }
    elementDisconnected(element) {
        const map = this._elementMap.get(element);
        if (!map) {
            return;
        }
        map.forEach((inst) => inst.disconnectedCallback?.(), this);
        this._elementMap.delete(element);
    }
    handleChange(attrName, el, oldVal) {
        let map = this._elementMap.get(el);
        if (!map) {
            this._elementMap.set(el, map = new Map());
        }
        let inst = map.get(attrName);
        const newVal = el.getAttribute(attrName);
        // Attribute is being created
        if (!inst) {
            const ctor = this.tryGetConstructor(attrName);
            if (!ctor) {
                throw new Error('No constructor found for attribute');
            }
            inst = new ctor(el);
            map.set(attrName, inst);
            inst.name = attrName;
            if (newVal === null) {
                throw new Error('Not possible!');
            }
            inst.value = newVal;
            inst.connectedCallback?.();
            return;
        }
        // Attribute was removed
        if (newVal === null) {
            inst.disconnectedCallback?.();
            map.delete(attrName);
        }
        else if (newVal !== inst.value) {
            inst.value = newVal;
            if (oldVal === null) {
                throw new Error('Not possible!');
            }
            inst.changedCallback?.(oldVal, newVal);
        }
    }
}
// Avoid errors trying to use DOM APIs in non-DOM environments (f.e. server-side rendering).
if (globalThis.window.document) {
    const _attachShadow = Element.prototype.attachShadow;
    Element.prototype.attachShadow = function attachShadow(options) {
        const root = _attachShadow.call(this, options);
        if (!root.customAttributes) {
            // Is it realy nessesary to create a new instance of CustomAttributeRegistry for each shadowRoot?
            root.customAttributes = customAttributes; // New CustomAttributeRegistry(root);
        }
        return root;
    };
}
//# sourceMappingURL=CustomAttributeRegistry.js.map