// #region Imports
import { on } from '@breadstone/mosaik-elements';
import { ControllerBase } from './Abstracts/ControllerBase';
/**
 * The `SlotController` class is a reactive controller that provides a set of methods to work with slots.
 *
 * @public
 */
export class SlotController extends ControllerBase {
    // #region Fields
    _slotChangeCallback;
    _mutationObserver;
    _slotChangeSubscription;
    // #endregion
    // #region Ctor
    /**
     * Constructs a new instance of the `SlotController` class.
     *
     * @public
     */
    constructor(host, config) {
        super(host);
        this._slotChangeCallback = config?.callback;
        this._slotChangeSubscription = null;
        this._mutationObserver = new MutationObserver((x) => this.handleMutation(x));
        this._mutationObserver.observe(this.host, {
            childList: true,
            subtree: true
        });
    }
    // #endregion
    // #region Methods
    /**
     * @public
     * @override
     */
    hostConnected() {
        if (this.host.shadowRoot) {
            this._slotChangeSubscription = on(this.host.shadowRoot, 'slotchange', (event) => {
                event.stopImmediatePropagation();
                this.handleChange(event.target);
            }, { passive: true });
        }
    }
    /**
     * @public
     * @override
     */
    hostDisconnected() {
        this._slotChangeSubscription?.dispose();
        this._mutationObserver.disconnect();
    }
    /**
     * Returns the assigned nodes of the slot.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    getAssignedNodes(slotName) {
        const slot = this.host.shadowRoot?.querySelector(slotName ? `slot[name="${slotName}"]` : 'slot:not([name])');
        return slot
            ? this.extractNodesFromSlot(slot)
            : this.extractNodesFromChildren(slotName ?? '');
    }
    /**
     * Returns the nodes of the slot.
     * If the slot name is not provided, the default slot is used.
     */
    getNodes(slotName) {
        const slot = this.host.shadowRoot?.querySelector(slotName ? `slot[name="${slotName}"]` : 'slot:not([name])');
        return Array.from(slot?.childNodes ?? []);
    }
    /**
     * Returns a boolean indicating whether the slot has assigned nodes.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    hasAssignedNodes(slotName) {
        return this.getAssignedNodes(slotName).length > 0;
    }
    /**
     * Returns a boolean indicating whether the slot has nodes.
     * If the slot name is not provided, the default slot is used.
     */
    hasNodes(slotName) {
        return this.getNodes(slotName).length > 0;
    }
    /**
     * Returns a boolean indicating whether the slot has assigned content.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    hasAssignedContent(slotName) {
        return this.getAssignedNodes(slotName)
            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
            .some((node) => (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) || node.nodeType === Node.ELEMENT_NODE);
    }
    /**
     * Returns a boolean indicating whether the slot has content.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    hasContent(slotName) {
        return this.getNodes(slotName)
            // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
            .some((node) => (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) || node.nodeType === Node.ELEMENT_NODE);
    }
    /**
     * Returns the assigned elements of the slot.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    getAssignedElements(slotName) {
        return this.getAssignedNodes(slotName).filter((node) => node instanceof Element);
    }
    /**
     * Returns the elements of the slot.
     * If the slot name is not provided, the default slot is used.
     */
    getElements(slotName) {
        return this.getNodes(slotName).filter((node) => node instanceof Element);
    }
    /**
     * Returns a boolean indicating whether the slot has assigned elements.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    hasAssignedElements(slotName) {
        return this.getAssignedElements(slotName).length > 0;
    }
    /**
     * Returns a boolean indicating whether the slot has elements.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The optional slot name.
     */
    hasElements(slotName) {
        return this.getElements(slotName).length > 0;
    }
    /**
     * Returns a boolean indicating whether the slot has assigned text content.
     * If the slot name is not provided, the default slot is used.
     *
     * @public
     * @param slotName - The slot name.
     */
    hasSlot(slotName) {
        return !!this.host.shadowRoot?.querySelector(`slot[name="${slotName}"]`);
    }
    /**
     * @private
     */
    extractNodesFromSlot(slot) {
        return slot.assignedNodes().flatMap((e) => e instanceof HTMLSlotElement ? this.extractNodesFromSlot(e) : [e]);
    }
    /**
     * @private
     */
    extractNodesFromChildren(slotName) {
        return Array.from(this.host.childNodes)
            .filter((node) => node instanceof Element || (node instanceof Text && !!node.textContent?.trim()))
            .filter((child) => child instanceof Element ? child.slot === slotName : !slotName)
            .flatMap((child) => child instanceof HTMLSlotElement ? child.assignedNodes() : [child]);
    }
    /**
     * @private
     */
    handleChange(target) {
        const slotName = target?.name;
        this.host.requestUpdate();
        this._slotChangeCallback?.(slotName);
    }
    /**
     * @private
     */
    handleMutation(mutations) {
        if (mutations.some((mutation) => mutation.type === 'childList')) {
            this.handleChange();
        }
    }
}
//# sourceMappingURL=SlotController.js.map