import * as i0 from '@angular/core';
import { inject, Renderer2, signal, computed, Directive, input, numberAttribute, booleanAttribute, contentChildren, contentChild, output, NgModule } from '@angular/core';
import { SharedResizeObserver } from '@angular/cdk/observers/private';
import { DOCUMENT } from '@angular/common';
import { toSignal, outputToObservable, toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { kbqInjectNativeElement, KBQ_WINDOW } from '@koobiq/components/core';
import { merge, switchMap, skip, debounceTime } from 'rxjs';

/**
 * Manages the visibility of the element.
 *
 * @docs-private
 */
class ElementVisibilityManager {
    constructor() {
        this.renderer = inject(Renderer2);
        this.hiddenState = signal(false);
        /**
         * @docs-private
         */
        this.element = kbqInjectNativeElement();
        /**
         * Whether the element is hidden.
         *
         * @docs-private
         */
        this.hidden = computed(() => this.hiddenState());
    }
    /**
     * Hides the element.
     *
     * @docs-private
     */
    hide() {
        this.renderer.setStyle(this.element, 'visibility', 'hidden');
        this.renderer.setStyle(this.element, 'position', 'absolute');
        this.hiddenState.set(true);
    }
    /**
     * Shows the element.
     *
     * @docs-private
     */
    show() {
        this.renderer.removeStyle(this.element, 'visibility');
        this.renderer.removeStyle(this.element, 'position');
        this.hiddenState.set(false);
    }
    /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ElementVisibilityManager, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: ElementVisibilityManager, host: { properties: { "attr.aria-hidden": "hidden()", "class.kbq-overflow-item-hidden": "hidden()" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ElementVisibilityManager, decorators: [{
            type: Directive,
            args: [{
                    host: {
                        '[attr.aria-hidden]': 'hidden()',
                        // @TODO should be refactored (#DS-3834)
                        '[class.kbq-overflow-item-hidden]': 'hidden()'
                    }
                }]
        }] });
/**
 * Directive for displaying the result of hidden items by the `KbqOverflowItems` directive.
 */
class KbqOverflowItemsResult extends ElementVisibilityManager {
    /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsResult, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: KbqOverflowItemsResult, isStandalone: true, selector: "[kbqOverflowItemsResult]", host: { classAttribute: "kbq-overflow-items-result" }, exportAs: ["kbqOverflowItemsResult"], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsResult, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[kbqOverflowItemsResult]',
                    exportAs: 'kbqOverflowItemsResult',
                    host: { class: 'kbq-overflow-items-result' }
                }]
        }] });
/**
 * Directive for the item that can be hidden by the `KbqOverflowItems` directive.
 */
class KbqOverflowItem extends ElementVisibilityManager {
    constructor() {
        super(...arguments);
        /**
         * Unique identifier for the item.
         */
        this.id = input.required({ alias: 'kbqOverflowItem' });
        /**
         * Defines the order in which the item is processed in the overflow container,
         * without changing its original position in the `QueryList`.
         */
        this.order = input(null, { transform: numberAttribute });
        /**
         * Element with this attribute will be ignored when hiding and will always remain visible.
         * @default false
         */
        this.alwaysVisible = input(false, { transform: booleanAttribute });
    }
    /**
     * @docs-private
     */
    hide() {
        if (this.alwaysVisible())
            return;
        super.hide();
    }
    /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItem, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
    /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.14", type: KbqOverflowItem, isStandalone: true, selector: "[kbqOverflowItem]", inputs: { id: { classPropertyName: "id", publicName: "kbqOverflowItem", isSignal: true, isRequired: true, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: false, transformFunction: null }, alwaysVisible: { classPropertyName: "alwaysVisible", publicName: "alwaysVisible", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.kbq-overflow-item_always-visible": "alwaysVisible()" }, classAttribute: "kbq-overflow-item" }, exportAs: ["kbqOverflowItem"], usesInheritance: true, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItem, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[kbqOverflowItem]',
                    exportAs: 'kbqOverflowItem',
                    host: {
                        class: 'kbq-overflow-item',
                        '[class.kbq-overflow-item_always-visible]': 'alwaysVisible()'
                    }
                }]
        }] });
/**
 * Directive for managing the visibility of items that overflow the container.
 */
class KbqOverflowItems {
    constructor() {
        this.element = kbqInjectNativeElement();
        this.resizeObserver = inject(SharedResizeObserver);
        this.renderer = inject(Renderer2);
        this.document = inject(DOCUMENT);
        this.window = inject(KBQ_WINDOW);
        /**
         * `KbqOverflowItem` directive references.
         */
        this.items = contentChildren(KbqOverflowItem);
        /**
         * `KbqOverflowItemsResult` directive reference.
         */
        this.result = contentChild(KbqOverflowItemsResult);
        /**
         * Whether the overflow order should be reversed.
         *
         * @example
         * ```
         * [0, 1, hidden, hidden] ---> [hidden, hidden, 2, 3]
         * ```
         *
         * @default false
         */
        this.reverseOverflowOrder = input(false, { transform: booleanAttribute });
        /**
         * Debounce time for recalculating items visibility.
         *
         * @default 0
         */
        this.debounceTime = input(0, { transform: numberAttribute });
        /**
         * List of additional elements to observe for resize changes.
         *
         * @default document.body
         */
        this.additionalResizeObserverTargets = input(this.document.body);
        /**
         * Emits when the set of hidden items changes.
         */
        this.changes = output();
        /**
         * Set of hidden item IDs.
         */
        this.hiddenItemIDs = toSignal(outputToObservable(this.changes), {
            initialValue: new Set([])
        });
        /**
         * Defines the orientation of the overflow items.
         *
         * @default 'horizontal'
         */
        this.orientation = input('horizontal');
        this.orientationConfig = {
            horizontal: {
                containerSize: (element) => element.clientWidth,
                paddingStart: ({ paddingLeft }) => parseFloat(paddingLeft) || 0,
                paddingEnd: ({ paddingRight }) => parseFloat(paddingRight) || 0,
                itemSize: (element) => {
                    const { marginRight, marginLeft } = this.window.getComputedStyle(element);
                    return element.offsetWidth + (parseFloat(marginLeft) || 0) + (parseFloat(marginRight) || 0);
                },
                flexDirection: 'row'
            },
            vertical: {
                containerSize: (element) => element.clientHeight,
                paddingStart: ({ paddingTop }) => parseFloat(paddingTop) || 0,
                paddingEnd: ({ paddingBottom }) => parseFloat(paddingBottom) || 0,
                itemSize: (element) => {
                    const { marginTop, marginBottom } = this.window.getComputedStyle(element);
                    return element.offsetHeight + (parseFloat(marginTop) || 0) + (parseFloat(marginBottom) || 0);
                },
                flexDirection: 'column'
            }
        };
        this.setStyles();
        this.setupObservers();
    }
    setupObservers() {
        const resizeObservers = merge(this.resizeObserver.observe(this.element), toObservable(this.additionalResizeObserverTargets).pipe(switchMap((targets) => {
            return Array.isArray(targets)
                ? merge(...targets.map((target) => this.resizeObserver.observe(target)))
                : this.resizeObserver.observe(targets);
        })));
        merge(toObservable(this.items), toObservable(this.reverseOverflowOrder).pipe(skip(1)), toObservable(this.orientation).pipe(skip(1)), resizeObservers)
            .pipe(debounceTime(this.debounceTime()), takeUntilDestroyed())
            .subscribe(() => {
            const hiddenItems = this.getHiddenItems(this.sortItemsByOrder(this.items()), this.reverseOverflowOrder(), this.result(), this.element, this.orientation());
            const hiddenItemIDs = new Set(hiddenItems.map(({ id }) => id()));
            this.changes.emit(hiddenItemIDs);
        });
    }
    /**
     * Sort items by their `order` attribute.
     * If an item does not have an `order` defined, its index is used as a fallback.
     */
    sortItemsByOrder(items) {
        const itemsWithOrder = Array.from(items, (item, index) => ({ item, order: item.order() ?? index }));
        return itemsWithOrder.sort((a, b) => a.order - b.order).map(({ item }) => item);
    }
    /**
     * Manages the visibility of items based on the available space in the container and returns the hidden items.
     * Direction of hiding is determined by the `reverseOverflowOrder` attribute.
     */
    getHiddenItems(items, reverseOverflowOrder, result, container, orientation) {
        result?.hide();
        items.forEach((item) => item.show());
        while (this.hasOverflown(container, items, result, orientation)) {
            const itemToHide = reverseOverflowOrder
                ? items.find(({ hidden, alwaysVisible }) => !hidden() && !alwaysVisible())
                : this.findLast(items, ({ hidden, alwaysVisible }) => !hidden() && !alwaysVisible());
            if (!itemToHide)
                break;
            itemToHide.hide();
            result?.show();
        }
        return items.filter(({ hidden }) => hidden());
    }
    /**
     * This method sets the necessary styles for the directive.
     */
    setStyles() {
        this.renderer.setStyle(this.element, 'position', 'relative');
        this.renderer.setStyle(this.element, 'overflow', 'hidden');
        this.renderer.setStyle(this.element, 'display', 'flex');
        this.renderer.setStyle(this.element, 'flex-grow', '1');
        this.renderer.setStyle(this.element, 'flex-wrap', 'nowrap');
        toObservable(this.orientation)
            .pipe(takeUntilDestroyed())
            .subscribe((orientation) => {
            const { flexDirection } = this.orientationConfig[orientation];
            this.renderer.setStyle(this.element, 'flex-direction', flexDirection);
        });
    }
    hasOverflown(container, items, result, orientation) {
        const { containerSize, paddingStart, paddingEnd, itemSize } = this.orientationConfig[orientation];
        const computedStyle = this.window.getComputedStyle(container);
        const containerSizeWithoutPaddings = containerSize(container) - paddingStart(computedStyle) - paddingEnd(computedStyle);
        const itemsSize = items.reduce((size, item) => size + (item.hidden() ? 0 : itemSize(item.element)), 0);
        const resultSize = !result || result.hidden() ? 0 : itemSize(result.element);
        return itemsSize + resultSize > containerSizeWithoutPaddings;
    }
    /**
     * @TODO Should be removed when `Array.prototype.findLast` is supported.
     */
    findLast(array, predicate) {
        for (let i = array.length - 1; i >= 0; i--) {
            if (predicate(array[i])) {
                return array[i];
            }
        }
        return undefined;
    }
    /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItems, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    /** @nocollapse */ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "18.2.14", type: KbqOverflowItems, isStandalone: true, selector: "[kbqOverflowItems]", inputs: { reverseOverflowOrder: { classPropertyName: "reverseOverflowOrder", publicName: "reverseOverflowOrder", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null }, additionalResizeObserverTargets: { classPropertyName: "additionalResizeObserverTargets", publicName: "additionalResizeObserverTargets", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changes: "changes" }, host: { classAttribute: "kbq-overflow-items" }, queries: [{ propertyName: "items", predicate: KbqOverflowItem, isSignal: true }, { propertyName: "result", first: true, predicate: KbqOverflowItemsResult, descendants: true, isSignal: true }], exportAs: ["kbqOverflowItems"], ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItems, decorators: [{
            type: Directive,
            args: [{
                    standalone: true,
                    selector: '[kbqOverflowItems]',
                    exportAs: 'kbqOverflowItems',
                    host: { class: 'kbq-overflow-items' }
                }]
        }], ctorParameters: () => [] });

const COMPONENTS = [
    KbqOverflowItems,
    KbqOverflowItem,
    KbqOverflowItemsResult
];
class KbqOverflowItemsModule {
    /** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    /** @nocollapse */ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsModule, imports: [KbqOverflowItems,
            KbqOverflowItem,
            KbqOverflowItemsResult], exports: [KbqOverflowItems,
            KbqOverflowItem,
            KbqOverflowItemsResult] }); }
    /** @nocollapse */ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsModule }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: KbqOverflowItemsModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: COMPONENTS,
                    exports: COMPONENTS
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { ElementVisibilityManager, KbqOverflowItem, KbqOverflowItems, KbqOverflowItemsModule, KbqOverflowItemsResult };
//# sourceMappingURL=koobiq-components-overflow-items.mjs.map
