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;
};
import { umbExtensionsRegistry } from '../../registry.js';
import { css, repeat, customElement, property, state, html, nothing } from '../../../../../external/lit/index.js';
import { UmbExtensionsElementInitializer, } from '../../../../../libs/extension-api/index.js';
import { UmbLitElement } from '../../../lit-element/index.js';
/**
 * A custom element that dynamically renders extensions registered in the extension registry.
 *
 * This element observes the extension registry and renders all permitted extensions matching the specified type(s).
 * Extensions are automatically filtered by their conditions and can be further filtered using the `filter` property.
 * The element handles extension lifecycle, property passing, and event binding.
 * @element umb-extension-slot
 * @slot default - Fallback content shown when no extensions are permitted (unless `fallbackRenderMethod` is provided).
 * @example Basic usage - Render all extensions of a type
 * ```html
 * <umb-extension-slot type="workspaceFooterApp"></umb-extension-slot>
 * ```
 * @example Multiple types - Render extensions from multiple types
 * ```html
 * <umb-extension-slot .type=${['headerApp', 'headerAppButton']}></umb-extension-slot>
 * ```
 * @example Single extension - Only render the first matching extension
 * ```html
 * <umb-extension-slot type="menu" single></umb-extension-slot>
 * ```
 * @example With filter - Filter extensions by manifest properties
 * ```html
 * <umb-extension-slot
 *     type="menu"
 *     .filter=${(manifest) => manifest.alias === 'My.Menu.Alias'}>
 * </umb-extension-slot>
 * ```
 * @example With props - Pass data to extension elements
 * ```html
 * <umb-extension-slot
 *     type="searchResultItem"
 *     .props=${{ item: searchResult }}>
 * </umb-extension-slot>
 * ```
 * @example With default element - Specify fallback element for extensions without one
 * ```html
 * <umb-extension-slot
 *     type="menu"
 *     default-element="umb-menu">
 * </umb-extension-slot>
 * ```
 * @example Combined usage - Filter, props, default element, and single
 * ```html
 * <umb-extension-slot
 *     type="searchResultItem"
 *     single
 *     .filter=${(manifest) => manifest.forEntityTypes.includes(item.entityType)}
 *     .props=${{ item }}
 *     default-element="umb-search-result-item">
 * </umb-extension-slot>
 * ```
 * @example Custom render method - Control how extensions are rendered
 * ```html
 * <umb-extension-slot
 *     type="blockEditorCustomView"
 *     single
 *     .filter=${this.#extensionSlotFilterMethod}
 *     .renderMethod=${(ext, index) => html`<div class="wrapper">${ext.component}</div>`}
 *     .props=${this._blockViewProps}>
 * </umb-extension-slot>
 * ```
 * @example Fallback content - Shows the slotted content when no extensions match
 * ```html
 * <umb-extension-slot type="myExtensionType">
 *     <p>No extensions available</p>
 * </umb-extension-slot>
 * ```
 * @example Fallback render method - Shows the result of the fallbackRenderMethod when no extensions match
 * ```html
 * <umb-extension-slot type="myExtensionType">
 *     .fallbackRenderMethod=${() => html`<p>No extensions available</p>`}
 * </umb-extension-slot>
 * ```
 */
// TODO: Refactor extension-slot and extension-with-api slot.
// TODO: Fire change event.
// TODO: Make property that reveals the amount of displayed/permitted extensions.
let UmbExtensionSlotElement = class UmbExtensionSlotElement extends UmbLitElement {
    #attached = false;
    #extensionsController;
    #disconnectAC;
    /**
     * The type or types of extensions to render. Required for the slot to display anything.
     * Can be a single type string or an array of type strings to render extensions from multiple types.
     * @example Single type
     * ```html
     * <umb-extension-slot type="workspaceFooterApp"></umb-extension-slot>
     * ```
     * @example Multiple types
     * ```html
     * <umb-extension-slot .type=${['my-extension-type','another-extension-type']}></umb-extension-slot>
     * ```
     */
    set type(value) {
        if (value === this.#type)
            return;
        this.#type = value;
        this.#observeExtensions();
    }
    get type() {
        return this.#type;
    }
    #type;
    /**
     * Filter function for extension manifests.
     * This is an initial filter taking effect before conditions or overwrites are applied.
     * Extensions will still be filtered by their manifest-defined conditions after this filter.
     * The filter function receives the extension manifest and should return true to include it.
     * @example Filter by manifest alias
     * ```html
     * <umb-extension-slot
     *     type="menu"
     *     .filter=${(manifest) => manifest.alias === 'My.Menu.Alias'}>
     * </umb-extension-slot>
     * ```
     * @example Filter by meta properties
     * ```html
     * <umb-extension-slot
     *     type="searchResultItem"
     *     .filter=${(manifest) => manifest.forEntityTypes.includes(entityType)}>
     * </umb-extension-slot>
     * ```
     */
    set filter(value) {
        if (value === this.#filter)
            return;
        this.#filter = value;
        this.#observeExtensions();
    }
    get filter() {
        return this.#filter;
    }
    #filter = () => true;
    /**
     * Properties to pass to all rendered extension elements.
     * These properties are spread onto each extension element instance.
     * Note: The extension's manifest is always passed automatically regardless of this setting.
     * @example Pass data to extension elements
     * ```html
     * <umb-extension-slot
     *     type="searchResultItem"
     *     .props=${{ item: searchResult, showDetails: true }}>
     * </umb-extension-slot>
     * ```
     */
    set props(newVal) {
        this.#props = newVal;
        if (this.#extensionsController) {
            this.#extensionsController.properties = newVal;
        }
    }
    get props() {
        return this.#props;
    }
    #props = {};
    /**
     * Event listeners to attach to all rendered extension elements.
     * The key is the event name, and the value is the event handler function.
     * Listeners are automatically added when extensions are rendered and removed on disconnect.
     * @example Listen for custom events from extensions
     * ```html
     * <umb-extension-slot
     *     type="myExtensionType"
     *     .events=${{
     *         'item-selected': (e) => this.#handleItemSelected(e),
     *         'action-clicked': (e) => this.#handleAction(e)
     *     }}>
     * </umb-extension-slot>
     * ```
     */
    set events(newVal) {
        this.#events = newVal;
        if (this.#extensionsController) {
            this.#addEventListenersToExtensionElement();
        }
    }
    get events() {
        return this.#events;
    }
    #events = {};
    connectedCallback() {
        super.connectedCallback();
        this.#attached = true;
        // Cancel any pending destruction if we're being reconnected (e.g., during a DOM move/sort)
        if (this.#disconnectAC) {
            this.#disconnectAC.abort();
            this.#disconnectAC = undefined;
            // Only skip re-initialization if the controller still exists
            if (this.#extensionsController) {
                return;
            }
        }
        this.#observeExtensions();
    }
    disconnectedCallback() {
        super.disconnectedCallback();
        this.#attached = false;
        // Abort any previously pending disconnect before scheduling a new one
        this.#disconnectAC?.abort();
        // Defer destruction to allow for reconnection during DOM moves/sorting
        // If reconnected before the microtask resolves, the AbortController cancels the callback
        const abortController = (this.#disconnectAC = new AbortController());
        queueMicrotask(() => {
            if (!abortController.signal.aborted) {
                this.#disconnectAC = undefined;
                this.#removeEventListenersFromExtensionElement();
                this.#extensionsController?.destroy();
                this.#extensionsController = undefined;
            }
        });
    }
    #observeExtensions() {
        if (!this.#attached)
            return;
        this.#extensionsController?.destroy();
        if (this.#type) {
            this.#extensionsController = new UmbExtensionsElementInitializer(this, umbExtensionsRegistry, this.#type, this.filter, (extensionControllers) => {
                this._permitted = extensionControllers;
                this.#addEventListenersToExtensionElement();
            }, undefined, // We can leave the alias undefined as we destroy this our selfs.
            this.defaultElement, {
                single: this.single,
            });
            this.#extensionsController.properties = this.#props;
        }
    }
    render() {
        // First renders something once _permitted is set, this is to avoid flickering. [NL]
        return this._permitted
            ? this._permitted.length > 0
                ? repeat(this._permitted, (ext) => ext.alias, this.#renderExtension)
                : this.#renderNothing()
            : nothing;
    }
    #renderNothing() {
        return this.fallbackRenderMethod ? this.fallbackRenderMethod() : html `<slot></slot>`;
    }
    #renderExtension = (ext, i) => {
        return this.renderMethod ? this.renderMethod(ext, i) : ext.component;
    };
    #addEventListenersToExtensionElement() {
        this._permitted?.forEach((initializer) => {
            const component = initializer.component;
            if (!component)
                return;
            const events = this.#events;
            if (!events)
                return;
            this.#removeEventListenersFromExtensionElement();
            Object.entries(events).forEach(([eventName, handler]) => {
                component.addEventListener(eventName, handler);
            });
        });
    }
    #removeEventListenersFromExtensionElement() {
        this._permitted?.forEach((initializer) => {
            const component = initializer.component;
            if (!component)
                return;
            const events = this.#events;
            if (!events)
                return;
            Object.entries(events).forEach(([eventName, handler]) => {
                component.removeEventListener(eventName, handler);
            });
        });
    }
    static { this.styles = css `
		:host {
			display: contents;
		}
	`; }
};
__decorate([
    state()
], UmbExtensionSlotElement.prototype, "_permitted", void 0);
__decorate([
    property({ type: Boolean })
], UmbExtensionSlotElement.prototype, "single", void 0);
__decorate([
    property({ type: String })
], UmbExtensionSlotElement.prototype, "type", null);
__decorate([
    property({ type: Object, attribute: false })
], UmbExtensionSlotElement.prototype, "filter", null);
__decorate([
    property({ type: Object, attribute: false })
], UmbExtensionSlotElement.prototype, "props", null);
__decorate([
    property({ type: Object, attribute: false })
], UmbExtensionSlotElement.prototype, "events", null);
__decorate([
    property({ type: String, attribute: 'default-element' })
], UmbExtensionSlotElement.prototype, "defaultElement", void 0);
__decorate([
    property({ attribute: false })
], UmbExtensionSlotElement.prototype, "renderMethod", void 0);
__decorate([
    property({ attribute: false })
], UmbExtensionSlotElement.prototype, "fallbackRenderMethod", void 0);
UmbExtensionSlotElement = __decorate([
    customElement('umb-extension-slot')
], UmbExtensionSlotElement);
export { UmbExtensionSlotElement };
