/* eslint-disable @typescript-eslint/no-empty-object-type */
/* eslint-disable @typescript-eslint/naming-convention */
// #region Imports
import { SlotController } from '../Controllers/SlotController';
/**
 * Slottable - A behavior that adds slottable capabilities to a control.
 *
 * @public
 */
export const Slottable = (base) => {
    class SlottableElement extends base {
        // #region Fields
        _slotController;
        // #endregion
        // #region Ctor
        /**
         * Constructs a new instance of the `SlottableElement` class.
         *
         * @public
         */
        constructor(...args) {
            super(args);
            this._slotController = new SlotController(this, {
                callback: (slotName) => {
                    this.onSlotChanges(slotName);
                }
            });
        }
        // #endregion
        // #region Methods
        /**
         * Assigns nodes to a dedicated slot.
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @param node - The nodes to assign to the slot.
         * @returns void
         */
        assignToSlot(slotName, node) {
            const slotSelector = slotName ? `slot[name="${slotName}"]` : 'slot:not([name])';
            const slot = this.shadowRoot?.querySelector(slotSelector);
            if (!slot) {
                const exceptionMessage = slotName
                    ? `The slot "${slotName}" was not found.`
                    : 'The default slot was not found.';
                throw new Error(exceptionMessage);
            }
            slot.assign(node);
        }
        /**
         * Returns the assigned elements of a slot.
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @param filter - The optional filter function.
         * @returns The assigned elements of a slot.
         */
        getSlotAssignments(slotName, filter) {
            const elements = this._slotController.getAssignedElements(slotName);
            if (filter) {
                return elements.filter((x) => filter(x));
            }
            return elements;
        }
        /**
         * Returns the assigned elements of a slot.
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @param element - The element to filter.
         * @returns The assigned elements of a slot.
         */
        getSlotAssignmentsOf(slotName, element) {
            return this.getSlotAssignments(slotName).filter((x) => x instanceof element);
        }
        /**
         * Returns the assigned elements of a slot as string.
         * This method extracts text content recursively, including from Shadow DOMs
         * and Custom Elements with known text properties (like `mosaik-text`).
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @returns The assigned elements of a slot as string.
         */
        getSlotContent(slotName) {
            const assignedNodes = this._slotController.getAssignedNodes(slotName);
            return assignedNodes.map((node) => this.extractTextContent(node)).join('');
        }
        /**
         * Extracts text content from a node, including from Shadow DOMs and
         * Custom Elements with known text properties.
         *
         * @private
         * @param node - The node to extract text from.
         * @returns The extracted text content.
         */
        extractTextContent(node) {
            // Text nodes: return content directly
            if (node.nodeType === Node.TEXT_NODE) {
                return node.textContent ?? '';
            }
            // Element nodes: check for known text properties first
            if (node instanceof HTMLElement) {
                // Check for known text properties on Custom Elements (e.g., mosaik-text)
                const textProperty = node.text;
                if (typeof textProperty === 'string' && textProperty.length > 0) {
                    return textProperty;
                }
                // If element has Shadow DOM, extract from there
                if (node.shadowRoot) {
                    return this.extractTextFromShadowRoot(node.shadowRoot);
                }
                // Otherwise, recursively extract from children
                return Array.from(node.childNodes)
                    .map((child) => this.extractTextContent(child))
                    .join('');
            }
            // Fallback to textContent for other node types
            return node.textContent ?? '';
        }
        /**
         * Extracts visible text content from a Shadow DOM.
         *
         * @private
         * @param shadowRoot - The shadow root to extract text from.
         * @returns The extracted text content.
         */
        extractTextFromShadowRoot(shadowRoot) {
            const texts = [];
            const walker = document.createTreeWalker(shadowRoot, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, {
                acceptNode: (visitedNode) => {
                    // Skip slot elements (they project content we've already processed)
                    if (visitedNode instanceof HTMLSlotElement) {
                        return NodeFilter.FILTER_REJECT;
                    }
                    return NodeFilter.FILTER_ACCEPT;
                }
            });
            let currentNode = walker.currentNode;
            while (currentNode) {
                if (currentNode.nodeType === Node.TEXT_NODE) {
                    const text = currentNode.textContent?.trim();
                    if (text) {
                        texts.push(text);
                    }
                }
                currentNode = walker.nextNode();
            }
            return texts.join(' ');
        }
        /**
         * Returns a boolean that indicates if a slot has content.
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @returns A boolean that indicates if a slot has content.
         */
        hasSlotContent(slotName) {
            const hasAssignedContent = this._slotController.hasAssignedContent(slotName);
            const hasContent = this._slotController.hasContent(slotName);
            return hasAssignedContent || hasContent;
        }
        /**
         * Returns a boolean that indicates if a slot exists.
         *
         * @public
         * @hidden
         * @param slotName - The slot name (For default slot, pass an empty string).
         * @returns A boolean that indicates if a slot exists.
         */
        hasSlot(slotName) {
            return this._slotController.hasSlot(slotName);
        }
        /**
         * Called when the slot changes.
         *
         * @public
         * @hidden
         * @param slotName - The optional slot name (For default slot, passes undefined).
         * @returns void
         */
        onSlotChanges(_slotName) {
            // Override
        }
    }
    return SlottableElement;
};
//# sourceMappingURL=Slottable.js.map