// #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 { EventListenerSubscription, HTMLElementEventEmitter, isEventListenerSubscription } from '@breadstone/mosaik-elements';
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';
// #endregion
/**
 * Custom Element - The foundational base class for all Mosaik web components.
 *
 * @description
 * CustomElement extends LitElement and provides the core infrastructure for all Mosaik components.
 * It establishes fundamental patterns including lifecycle management, event handling, style adoption,
 * internationalization (i18n) support, and template part querying. This abstract class serves as the
 * root of the component hierarchy and provides essential features like custom style injection via
 * shadow DOM, automatic event subscription cleanup, property change tracking, and bidirectional text
 * support. All Mosaik components inherit from this base class either directly or through specialized
 * intermediate base classes.
 *
 * @name CustomElement
 * @category Abstract Elements
 *
 * @slot style - Custom styles injection slot for shadow DOM styling escape hatch
 *
 * @fires connected {ConnectedEvent} - Emitted when the element is connected to the DOM
 * @fires disconnected {DisconnectedEvent} - Emitted when the element is disconnected from the DOM
 * @fires changed {PropertyChangedEvent} - Emitted when any attribute changes before update
 *
 * @example
 * Extending CustomElement to create a new component:
 * ```typescript
 * export class MyElement extends CustomElement {
 *   constructor() {
 *     super();
 *   }
 *   protected override render() {
 *     return html`<div>My Component</div>`;
 *   }
 * }
 * ```
 *
 * @example
 * Injecting custom styles via the style slot:
 * ```html
 * <mosaik-card>
 *   <style slot="style">
 *     .title { color: purple; font-size: 2rem; }
 *   </style>
 *   <h3 class="title">Custom Title</h3>
 * </mosaik-card>
 * ```
 *
 * @example
 * Using lifecycle events:
 * ```typescript
 * element.connected.on(() => {
 *   console.log('Element connected to DOM');
 * });
 * element.changed.on(({ propertyName, oldValue, newValue }) => {
 *   console.log(`Property ${propertyName} changed from ${oldValue} to ${newValue}`);
 * });
 * ```
 *
 * @example
 * Querying template parts:
 * ```typescript
 * protected override onApplyTemplate(): void {
 *   const header = this.getTemplatePart<HTMLElement>('header');
 *   const items = this.getTemplateParts<HTMLElement>('item');
 * }
 * ```
 *
 * @abstract
 * @public
 */
export class CustomElement extends LitElement {
    // #region Fields
    /**
     * The slot name used for custom styles injection.
     * Users can inject custom styles via `<style slot="style">...</style>`.
     *
     * @public
     * @readonly
     */
    static get CUSTOM_STYLES_SLOT() {
        return 'style';
    }
    _connected;
    _disconnected;
    _changed;
    _subscriptions;
    _dir;
    _lang;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `CustomElement` class.
     *
     * @public
     */
    constructor() {
        super();
        this._dir = 'auto';
        this._lang = document.documentElement.lang; // Default to document language
        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
    /**
     * The dir property indicates the directionality of the element's text.
     *
     * Supported values are:
     * - `ltr`: Left-to-Right text direction (e.g. English, German)
     * - `rtl`: Right-to-Left text direction (e.g. Arabic, Hebrew)
     * - `auto`: Automatically determine direction based on content (default)
     *
     * @public
     * @override
     * @attr dir
     */
    get dir() {
        return this._dir;
    }
    set dir(value) {
        if (this._dir !== value) {
            this._dir = value;
            this.requestUpdate('dir');
        }
    }
    /**
     * The lang property indicates the language of the element's content.
     *
     * @public
     * @override
     * @attr lang
     */
    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
     * @hidden
     * @eventProperty
     * @readonly
     */
    get connected() {
        return this._connected;
    }
    /**
     * Called when the element is disconnected from the DOM.
     * Provides reference to `IEventEmitter` as event argument.
     *
     * @public
     * @hidden
     * @eventProperty
     * @readonly
     */
    get disconnected() {
        return this._disconnected;
    }
    /**
     * Called when the element will be updated.
     * Provides reference to `IEventEmitter` as event argument.
     *
     * @public
     * @hidden
     * @eventProperty
     * @readonly
     */
    get changed() {
        return this._changed;
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     * @hidden
     */
    connectedCallback() {
        super.connectedCallback();
        this._connected.emit();
    }
    /**
     * @public
     * @override
     * @hidden
     */
    disconnectedCallback() {
        super.disconnectedCallback();
        this._subscriptions.forEach((subscriptions) => {
            Array.from(subscriptions).forEach((subscription) => subscription.dispose());
        });
        this._subscriptions.clear();
        this._disconnected.emit();
    }
    /**
     * @public
     * @override
     * @hidden
     */
    attributeChangedCallback(name, old, value) {
        super.attributeChangedCallback(name, old, value);
        if (old !== value) {
            this._changed.emit({
                propertyName: name,
                oldValue: old,
                newValue: value
            });
        }
    }
    /**
     * Adopts the specified styles.
     *
     * @public
     * @hidden
     * @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) {
        const typedListener = listener;
        this.addEventListener(event, typedListener, options);
        const subscription = EventListenerSubscription.create(event, typedListener, (disposable) => {
            this.removeEventListener(event, typedListener, options);
            this.unregisterSubscription(disposable);
        });
        this.registerSubscription(event, subscription);
        return subscription;
    }
    /**
     * @hidden
     */
    off(...args) {
        if (isEventListenerSubscription(args[0])) {
            args[0].dispose();
            return;
        }
        const event = args[0];
        const listener = args[1];
        const subscriptions = this._subscriptions.get(event);
        if (!subscriptions) {
            return;
        }
        const subscription = Array.from(subscriptions).find((candidate) => candidate.listener === listener);
        if (!subscription) {
            return;
        }
        subscription.dispose();
    }
    /**
     * @hidden
     */
    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.adoptCustomStyles();
        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
    }
    /**
     * Adopts custom styles from the light DOM into the shadow DOM.
     *
     * This method enables the "styling escape hatch" pattern, allowing developers
     * to inject custom styles into a component's shadow DOM via a slotted style element:
     *
     * @example
     * ```html
     * <mosaik-card>
     *   <style slot="custom-styles">
     *     .title { color: purple; font-size: 2rem; }
     *   </style>
     *   <h3 class="title">Custom Title</h3>
     * </mosaik-card>
     * ```
     *
     * @remarks
     * - Only `<style>` elements with `slot="custom-styles"` are processed.
     * - Uses `textContent` instead of `innerHTML` to prevent XSS attacks.
     * - Styles are adopted via `CSSStyleSheet` API for optimal performance.
     * - The style element remains in the light DOM but its content is adopted.
     * - This method is called automatically during `firstUpdated`.
     * - Custom styles are appended after component styles, allowing overrides.
     *
     * @protected
     */
    adoptCustomStyles() {
        if (!this.shadowRoot) {
            return;
        }
        const slotName = this.constructor.CUSTOM_STYLES_SLOT;
        const customStyleElement = this.querySelector(`style[slot="${slotName}"]`);
        if (customStyleElement?.tagName !== 'STYLE') {
            return;
        }
        // Use textContent to avoid XSS from innerHTML
        const cssText = customStyleElement.textContent;
        if (cssText.trim().length === 0) {
            return;
        }
        try {
            // Create a new CSSStyleSheet and adopt it
            const sheet = new CSSStyleSheet();
            sheet.replaceSync(cssText);
            // Append to existing adopted stylesheets
            this.shadowRoot.adoptedStyleSheets = [
                ...this.shadowRoot.adoptedStyleSheets,
                sheet
            ];
        }
        catch (error) {
            console.warn(`[${this.localName}] Failed to adopt custom styles:`, error instanceof Error ? error.message : error);
        }
    }
    /**
     * 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.
     * @param mode - a part match mode.
     */
    getTemplatePart(name, mode) {
        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, buildPartSelector(name.trim(), mode));
        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, buildPartSelector());
        }
        else {
            children = DomServiceLocator.current.findDescendants(root, buildPartSelector(name.trim(), 'exact'));
        }
        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(buildPartSelector()));
        }
        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();
    }
    /**
     * @private
     */
    registerSubscription(event, subscription) {
        if (!this._subscriptions.has(event)) {
            this._subscriptions.set(event, new Set());
        }
        const sub = this._subscriptions.get(event);
        if (!sub) {
            return;
        }
        sub.add(subscription);
    }
    /**
     * @private
     */
    unregisterSubscription(subscription) {
        const subscriptions = this._subscriptions.get(subscription.eventName);
        if (!subscriptions) {
            return;
        }
        subscriptions.delete(subscription);
        if (!subscriptions.size) {
            this._subscriptions.delete(subscription.eventName);
        }
    }
}
__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);
/**
 * Builds a CSS selector for querying elements by their `part` attribute.
 *
 * This helper mirrors native CSS attribute selector semantics and can be
 * safely used for DOM querying wherever CSS selectors are accepted
 * (e.g. `querySelector`, `querySelectorAll`).
 *
 * @private
 * @param partName - The name of the part to match. Required for all modes except `exists`.
 * @param mode - The matching strategy to apply. Defaults to `exact`.
 * @returns A CSS selector string targeting the `part` attribute.
 */
function buildPartSelector(partName, mode = 'exact') {
    switch (mode) {
        case 'exists':
            return '[part]';
        case 'token':
            return `[part~="${partName}"]`;
        case 'prefix':
            return `[part^="${partName}"]`;
        case 'suffix':
            return `[part$="${partName}"]`;
        case 'contains':
            return `[part*="${partName}"]`;
        case 'exact':
        default:
            return `[part="${partName}"]`;
    }
}
//# sourceMappingURL=CustomElement.js.map