// #region Imports
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { LitElement, nothing } from 'lit';
import { DomServiceLocator } from '../../../Dom/Dom';
import { Emit } from '../../../Events/Decorators/EmitDecorator';
import { Attribute } from '../../Decorators/AttributeDecorator';
import { FlowDirection } from '../../Types/FlowDirection';
import { HTMLElementEventEmitter, EventListenerSubscription } from '@breadstone/mosaik-elements';
import { isEventListenerSubscription } from '@breadstone/mosaik-elements';
/**
 * The base class of all custom components.
 *
 * @fires connected {CustomEvent<IEventDetail>} - Called when the element is connected to the DOM.
 * @fires disconnected {CustomEvent<IEventDetail>} - Called when the element is disconnected from the DOM.
 * @fires changed {IPropertyChangedEvent} - Called when the element will be updated.
 *
 * @public
 */
export class CustomElement extends LitElement {
    // #region Fields
    _connected;
    _disconnected;
    _changed;
    _subscriptions;
    _dir;
    _lang;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `CustomElement` class.
     *
     * @public
     */
    constructor() {
        super();
        this._dir = FlowDirection.Auto;
        this._lang = this.getRootLang();
        this._subscriptions = new Map();
        this._connected = new HTMLElementEventEmitter(this, 'connected');
        this._disconnected = new HTMLElementEventEmitter(this, 'disconnected');
        this._changed = new HTMLElementEventEmitter(this, 'changed');
    }
    // #endregion
    // #region Properties
    /**
     * Gets or sets the `dir` property.
     *
     * @public
     * @override
     * @attr
     */
    get dir() {
        return this._dir;
    }
    set dir(value) {
        if (this._dir !== value) {
            this._dir = value;
            this.requestUpdate('dir');
        }
    }
    /**
     * Gets or sets the `lang` property.
     *
     * @public
     * @override
     * @attr
     */
    get lang() {
        return this._lang;
    }
    set lang(value) {
        if (this._lang !== value) {
            this._lang = value;
            this.requestUpdate('lang');
        }
    }
    /**
     * Called when the element is connected to the DOM.
     * Provides reference to `IEventEmitter` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get connected() {
        return this._connected;
    }
    /**
     * Called when the element is disconnected from the DOM.
     * Provides reference to `IEventEmitter` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get disconnected() {
        return this._disconnected;
    }
    /**
     * Called when the element will be updated.
     * Provides reference to `IEventEmitter` as event argument.
     *
     * @public
     * @eventProperty
     * @readonly
     */
    get changed() {
        return this._changed;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    connectedCallback() {
        super.connectedCallback();
        this._connected.emit();
    }
    /**
     * @public
     * @override
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._subscriptions.forEach((v, k) => {
            this.removeEventListener(k, v.listener);
        });
        this._disconnected.emit();
    }
    /**
     * @public
     * @override
     */
    attributeChangedCallback(name, old, value) {
        super.attributeChangedCallback(name, old, value);
        if (old !== value) {
            this._changed.emit({
                propertyName: name,
                value: value
            });
        }
    }
    /**
     * Adopts the specified styles.
     *
     * @public
     * @param styles - The styles to adopt.
     */
    adoptStyle(styles) {
        if (Array.isArray(styles)) {
            const styleSheets = Array.from(styles)
                .map((style) => style.styleSheet)
                .filter((style) => style !== undefined);
            if (this.shadowRoot) {
                this.shadowRoot.adoptedStyleSheets = styleSheets;
            }
        }
        else {
            const styleSheet = styles.styleSheet;
            if (this.shadowRoot && styleSheet) {
                this.shadowRoot.adoptedStyleSheets = [styleSheet];
            }
        }
    }
    /**
     * Adds an event listener.
     * The listener will be removed when the element is disconnected from the DOM.
     *
     * @public
     * @hidden
     */
    on(event, listener, options) {
        this.addEventListener(event, listener, options);
        const subscription = EventListenerSubscription.create(event, listener, (disposable) => this.off(disposable));
        this._subscriptions.set(event, subscription);
        return subscription;
    }
    off(...args) {
        const event = isEventListenerSubscription(args[0])
            ? args[0].eventName
            : args[0];
        const listener = isEventListenerSubscription(args[0])
            ? args[0].listener
            : args[1];
        this.removeEventListener(event, listener);
        this._subscriptions.delete(event);
    }
    emit(...args) {
        const nameOrInstance = args.at(0);
        const init = args.at(1);
        let event = null;
        if (typeof nameOrInstance === 'string') {
            event = new CustomEvent(nameOrInstance, {
                bubbles: true,
                cancelable: false,
                composed: true,
                detail: {},
                ...init
            });
        }
        else {
            event = nameOrInstance;
        }
        return this.dispatchEvent(event);
    }
    /**
     * @public
     * @override
     * @hidden
     */
    requestUpdate(name, oldValue, options) {
        super.requestUpdate(name, oldValue, options);
    }
    /**
     * @protected
     * @override
     */
    firstUpdated(changedProperties) {
        super.firstUpdated(changedProperties);
        this.onApplyTemplate();
    }
    /**
     * @protected
     * @override
     */
    update(changedProperties) {
        super.update(changedProperties);
    }
    /**
     * @protected
     * @override
     */
    willUpdate(changedProperties) {
        super.willUpdate(changedProperties);
    }
    /**
     * A method that will be called when the element template is applied.
     * In this method are the element children available.
     *
     * @protected
     * @virtual
     */
    onApplyTemplate() {
        // override
    }
    /**
     * Returns the template part with the specified name.
     * The template child is a child element with the `part` attribute.
     *
     * @protected
     *
     * @param name - a part name.
     */
    getTemplatePart(name) {
        let root = null;
        const owner = this.localName || this.tagName;
        if (this.renderRoot !== this.shadowRoot) {
            root = this.renderRoot;
        }
        else {
            root = this.shadowRoot;
        }
        if (!this.isConnected) {
            const msg = `Cannot access any template child because the element '${name}' is not connected to the DOM.`;
            throw new Error(msg);
        }
        const child = DomServiceLocator.current.findDescendant(root, `[part="${name}"]`);
        if (!child) {
            const msg = `The template child with name '${name}' not found in ${owner}.\nCheck the 'part' attribute of the child element.\ne.g. <div part="${name}"></div>\n`;
            throw new Error(msg);
        }
        return child;
    }
    /**
     * Returns the template parts with the specified name.
     * The template parts are elements with the `part` attribute.
     * If the name is empty, all template parts will be returned.
     *
     * @protected
     *
     * @param name - a part name.
     */
    getTemplateParts(name) {
        let root = null;
        if (this.renderRoot !== this.shadowRoot) {
            root = this.renderRoot;
        }
        else {
            root = this.shadowRoot;
        }
        if (!this.isConnected) {
            const msg = 'Cannot access any template child because the element is not connected to the DOM.';
            throw new Error(msg);
        }
        let children = null;
        if (name.trim() === '') {
            children = DomServiceLocator.current.findDescendants(root, '[part]');
        }
        else {
            children = DomServiceLocator.current.findDescendants(root, `[part="${name.trim()}"]`);
        }
        if (!children) {
            const msg = `The template children with name '${name}' not found.`;
            throw new Error(msg);
        }
        return children;
    }
    /**
     * Returns all template parts.
     *
     * @protected
     */
    getAllTemplateParts() {
        if (this.shadowRoot) {
            return Array.from(this.shadowRoot.querySelectorAll('[part]'));
        }
        const msg = 'Cannot access any template children because the \'shadowRoot\' is not ready.';
        throw new Error(msg);
    }
    /**
     * Renders the element.
     *
     * @protected
     * @virtual
     */
    render() {
        return this.hidden
            ? nothing
            : super.render();
    }
    /**
     * Returns the lang attribute of the absolute root node in the DOM.
     *
     * @private
     */
    getRootLang() {
        return document.documentElement.lang;
    }
}
__decorate([
    Emit({ bubbles: false }),
    __metadata("design:type", Object)
], CustomElement.prototype, "_connected", void 0);
__decorate([
    Emit({ bubbles: false }),
    __metadata("design:type", Object)
], CustomElement.prototype, "_disconnected", void 0);
__decorate([
    Emit({ bubbles: false }),
    __metadata("design:type", Object)
], CustomElement.prototype, "_changed", void 0);
__decorate([
    Attribute({ type: FlowDirection }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CustomElement.prototype, "dir", null);
__decorate([
    Attribute({ type: String }),
    __metadata("design:type", String),
    __metadata("design:paramtypes", [String])
], CustomElement.prototype, "lang", null);
//# sourceMappingURL=CustomElement.js.map