import { __decorate, __metadata } from 'tslib';
import * as i0 from '@angular/core';
import { Injectable, INJECTOR, Optional, SkipSelf, InjectionToken, TemplateRef, Directive, Inject, isDevMode, EventEmitter, signal, ViewContainerRef, ChangeDetectorRef, Component, ChangeDetectionStrategy, Input, ContentChild, Output, ViewChild, HostListener } from '@angular/core';
import { UseFormControl, RxapFormControl, RxapForm, RxapFormBuilder, RXAP_FORM_DEFINITION, RXAP_FORM_INITIAL_STATE } from '@rxap/forms';
import * as i8 from '@angular/cdk/portal';
import { TemplatePortal, PortalModule } from '@angular/cdk/portal';
import { FlatTreeControl } from '@angular/cdk/tree';
import { NgStyle, NgIf, AsyncPipe, NgClass, NgForOf } from '@angular/common';
import * as i5 from '@angular/material/button';
import { MatButtonModule } from '@angular/material/button';
import * as i4 from '@angular/material/checkbox';
import { MatCheckboxModule } from '@angular/material/checkbox';
import * as i7 from '@angular/material/divider';
import { MatDividerModule } from '@angular/material/divider';
import * as i3 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i1 from '@angular/material/progress-bar';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import * as i6 from '@angular/material/progress-spinner';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import * as i2 from '@angular/material/tree';
import { MatTreeModule } from '@angular/material/tree';
import { ContenteditableDirective } from '@rxap/contenteditable';
import { Node } from '@rxap/data-structure-tree';
import { IconDirective } from '@rxap/material-directives/icon';
import { coerceArray, getIdentifierPropertyValue, joinPath, Required, DebounceCall } from '@rxap/utilities';
import { switchMap, map, tap, startWith } from 'rxjs/operators';
import { SelectionModel } from '@angular/cdk/collections';
import { BaseDataSource, RXAP_DATA_SOURCE_METADATA, RxapDataSource } from '@rxap/data-source';
import { ToggleSubject } from '@rxap/rxjs';
import { BehaviorSubject, Subject, combineLatest, debounceTime, from, merge } from 'rxjs';

let SearchForm = class SearchForm {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: SearchForm, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: SearchForm }); }
};
__decorate([
    UseFormControl(),
    __metadata("design:type", RxapFormControl)
], SearchForm.prototype, "search", void 0);
__decorate([
    UseFormControl(),
    __metadata("design:type", RxapFormControl)
], SearchForm.prototype, "scope", void 0);
SearchForm = __decorate([
    RxapForm({
        id: 'search',
        autoSubmit: 500,
    })
], SearchForm);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: SearchForm, decorators: [{
            type: Injectable
        }], propDecorators: { search: [], scope: [] } });
function FormFactory(injector, state, existingFormDefinition) {
    if (existingFormDefinition) {
        return existingFormDefinition;
    }
    return new RxapFormBuilder(SearchForm, injector).build(state ?? {});
}
const SearchFormProviders = [
    SearchForm,
    {
        provide: RXAP_FORM_DEFINITION,
        useFactory: FormFactory,
        deps: [
            INJECTOR,
            [new Optional(), RXAP_FORM_INITIAL_STATE],
            [new SkipSelf(), new Optional(), RXAP_FORM_DEFINITION],
        ],
    },
];

const RXAP_TREE_CONTENT_EDITABLE_METHOD = new InjectionToken('rxap/tree-content-editable-method');

class TreeContentDirective {
    constructor(template) {
        this.template = template;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeContentDirective, deps: [{ token: TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.0.6", type: TreeContentDirective, isStandalone: true, selector: "ng-template[rxapTreeContent]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeContentDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: 'ng-template[rxapTreeContent]',
                    standalone: true,
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{
                    type: Inject,
                    args: [TemplateRef]
                }] }] });

function isSelectionChange(obj) {
    return !!obj && obj['added'] !== undefined && obj['removed'] !== undefined;
}
const RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD = new InjectionToken('rxap/tree/data-source/root-remote-method');
const RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD = new InjectionToken('rxap/tree/data-source/children-remote-method');
const RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD = new InjectionToken('rxap/tree/data-source/apply-filter-method');
function flatTree(tree, all = false) {
    tree = coerceArray(tree);
    function flat(acc, list) {
        return [...acc, ...list];
    }
    const _flatTree = (node) => {
        if (!Array.isArray(node.children)) {
            if (isDevMode()) {
                console.log(node);
            }
            throw new Error('Node has not defined children');
        }
        if (all || node.expanded) {
            return [
                node,
                ...node.children.map((child) => _flatTree(child)).reduce(flat, []),
            ];
        }
        else {
            return [node];
        }
    };
    return tree
        .map((child) => _flatTree(child))
        .reduce((acc, items) => [...acc, ...items], []);
}
class DefaultTreeApplyFilterMethod {
    constructor() {
        this.lastFilter = null;
    }
    call({ tree, filter, scopeTypes }) {
        const nodes = flatTree(tree, true);
        // if (this.isEqualToLastFilter(filter)) {
        //   return flatTree(tree, false).filter(node => node.isVisible);
        // }
        const hasScopeFilter = (filter.scope &&
            Object.keys(filter.scope) &&
            Object.values(filter.scope).some((list) => list.length > 0));
        // if not scope and the search filter is an empty string, collapse all non-root nodes
        if (!filter.search && filter.search !== this.lastFilter?.search) {
            nodes
                .filter(node => node.parent)
                .forEach(node => node.collapse({ quite: true }));
        }
        if (filter.search || hasScopeFilter) {
            nodes.forEach(node => node.hide());
            for (const [type, list] of Object.entries(filter.scope ?? {})) {
                for (const node of nodes) {
                    if (node.type === type) {
                        if (list.some(item => getIdentifierPropertyValue(item) === node.id)) {
                            node.show({ forEachChildren: true });
                        }
                    }
                }
            }
            if (filter.search) {
                for (const node of nodes) {
                    if (hasScopeFilter && node.hidden) {
                        // if the filter has a scope filter. Only check the search filter on nodes that are shown by the scope
                        // filter
                        continue;
                    }
                    const display = node.display?.toLowerCase();
                    if (display) {
                        if (display.includes(filter.search.toLowerCase())) {
                            node.show({ parents: true });
                        }
                        else {
                            node.hide();
                        }
                    }
                    else {
                        // if node has no display, it is not shown
                        node.hide();
                    }
                }
                // hide each node that has no visible children
                // there exists the edge case that a node has visible children, but the node.hide() function is called
                // after the child.show({ parent:true })
                for (const node of nodes.filter(n => n.hidden && n.hasChildren && n.children.some(child => child.isVisible))) {
                    if (node.hidden) {
                        console.warn('Edge case detected. Node has visible children but is hidden.');
                        node.show();
                    }
                }
                // expand each node that has visible children
                for (const node of nodes.filter(n => n.hasChildren)) {
                    if (node.children.some(child => child.isVisible)) {
                        // set quite to true to prevent the tree from reloading -> this would result in a infinite loop
                        node.expand({ quite: true });
                    }
                }
            }
        }
        else {
            nodes.forEach(node => node.show());
        }
        this.lastFilter = filter;
        return flatTree(tree, false).filter(node => node.isVisible);
    }
    isEqualToLastFilter(filter) {
        if (this.lastFilter) {
            if (this.lastFilter.search === filter.search) {
                if (this.lastFilter.scope && filter.scope) {
                    if (Object.keys(this.lastFilter.scope).every(key => Object.keys(filter.scope).includes(key))) {
                        if (Object.entries(this.lastFilter.scope)
                            .every(([key, scope]) => scope.some(item => filter.scope[key].includes(item)))) {
                            return true;
                        }
                    }
                }
                if (filter.scope === this.lastFilter.scope) {
                    return true;
                }
            }
        }
        return false;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: DefaultTreeApplyFilterMethod, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: DefaultTreeApplyFilterMethod }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: DefaultTreeApplyFilterMethod, decorators: [{
            type: Injectable
        }] });
let TreeDataSource = class TreeDataSource extends BaseDataSource {
    constructor(rootRemoteMethod, childrenRemoteMethod = null, applyFilterMethod = null, metadata = null) {
        super(metadata);
        this.rootRemoteMethod = rootRemoteMethod;
        this.childrenRemoteMethod = childrenRemoteMethod;
        this.tree$ = new BehaviorSubject([]);
        // TODO : änlich problem wie bei der redundaten expand SelectionModel.
        this.loading$ = new ToggleSubject(true);
        this.searchForm = null;
        this._data$ = new BehaviorSubject([]);
        this._expandedLocalStorageSubscription = null;
        this._selectedLocalStorageSubscription = null;
        // im localStorage wird nur die id gespeichert.
        this._preSelected = [];
        this._refreshMatchFilter = new Subject();
        this._nodeParameters = null;
        this.toDisplay = () => 'to display function not defined';
        this.getIcon = () => null;
        this.getType = () => null;
        this.getStyle = () => ({});
        this.hasDetails = () => true;
        this.matchFilter = () => true;
        this.applyFilterMethod = applyFilterMethod ?? new DefaultTreeApplyFilterMethod();
        // TODO add new SelectModel class that saves the select model to the localStorage
        this.initSelected();
        this.initExpanded();
    }
    get nodeParameters() {
        return this._nodeParameters;
    }
    set nodeParameters(nodeParameters) {
        this._nodeParameters = nodeParameters;
        this.tree$.value.forEach(node => node.parameters = nodeParameters);
    }
    ngOnInit() {
        if (this.searchForm) {
            combineLatest([
                this.tree$,
                this.searchForm.rxapFormGroup.value$.pipe(debounceTime(1000)),
            ]).pipe(switchMap(async ([tree, filter]) => await this.applyFilterMethod.call({
                tree,
                filter,
                scopeTypes: this.metadata?.scopeTypes,
            })), map(nodes => coerceArray(nodes)))
                .subscribe(data => this._data$.next(data));
        }
        else {
            this.tree$.pipe(map(tree => flatTree(tree).filter(node => node.isVisible)), tap(nodes => nodes.forEach(node => node.show()))).subscribe(data => this._data$.next(data));
        }
    }
    async getTreeRoot(options = {}) {
        this.loading$.enable();
        const root = await this.getRoot(options);
        let rootNodes;
        if (Array.isArray(root)) {
            rootNodes = await Promise.all(root.map((node) => this.toNode(null, node)));
        }
        else {
            rootNodes = [await this.toNode(null, root)];
        }
        const tmpSelectedNodes = [];
        const restoreExpandAndSelectedState = (node) => {
            if (this.expanded.isSelected(node.id)) {
                node._expanded = true;
            }
            if (this.selected.selected.some(n => n.id === node.id)) {
                console.log('restore selected', node.display);
                node._selected = true;
                tmpSelectedNodes.push(node);
            }
            node.children.forEach(restoreExpandAndSelectedState);
        };
        rootNodes.forEach(restoreExpandAndSelectedState);
        console.log('restore expand and selected state', tmpSelectedNodes);
        this.selected.setSelection(...tmpSelectedNodes);
        this.tree$.next(rootNodes);
        this.loading$.disable();
        return rootNodes;
    }
    selectNode(node) {
        if (!this.selected.isMultipleSelection()) {
            if (this.selected.hasValue()) {
                const currentSelectedNode = this.selected.selected[0];
                currentSelectedNode.deselect();
            }
        }
        this.selected.select(node);
        node.parent?.expand();
        return Promise.resolve();
    }
    setTreeControl(treeControl) {
        this.treeControl = treeControl;
    }
    setMatchFilter(matchFilter) {
        this.matchFilter = matchFilter;
    }
    setToDisplay(toDisplay = this.toDisplay) {
        this.toDisplay = toDisplay;
    }
    setGetIcon(getIcon = this.getIcon) {
        this.getIcon = getIcon;
    }
    setHasDetails(hasDetails = this.hasDetails) {
        this.hasDetails = hasDetails;
    }
    deselectNode(node) {
        this.selected.deselect(node);
        return Promise.resolve();
    }
    async expandNode(node, options) {
        if (node.parent && !node.parent.expanded) {
            console.log('expand parent', node.parent.display);
            node.parent?.expand({ quite: true });
        }
        if (!options?.onlySelf) {
            // required to sync the expanstion state with the tree control
            // if the collpase is trigged by node.expand this state is not
            // sync with the tree control
            this.treeControl.expansionModel.select(node);
        }
        if (node.item.hasChildren && !node.item.children?.length) {
            node.isLoading$.enable();
            const children = await this.getChildren(node);
            // add the loaded children to the item object
            node.item.children = children;
            node.addChildren(await Promise.all(children.map((child) => this.toNode(node, child, node.depth + 1, node.onExpand, node.onCollapse))));
            node.isLoading$.disable();
        }
        console.log('expand node', node.display);
        this.expanded.select(node.id);
        // node.parent?.expand({quite: true});
        if (!options?.quite) {
            this.tree$.next(this.tree$.value);
        }
    }
    async getChildren(node) {
        if (!this.childrenRemoteMethod) {
            throw new Error(`The node '${node.id}' has unloaded children but the RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD is not provided.`);
        }
        return this.childrenRemoteMethod.call(node);
    }
    async getRoot(options = {}) {
        const rootParameters = await this.getRootParameters(options);
        return this.rootRemoteMethod.call(rootParameters);
    }
    async getRootParameters(options = {}) {
        return options;
    }
    // TODO : find better solution to allow the overwrite of the toNode method
    // without losing the custom preselect and preexpand function
    getNodeById(id) {
        function getNodeById(node, nodeId) {
            if (node.id === nodeId) {
                return node;
            }
            if (node.hasNode(nodeId)) {
                return node.getNode(nodeId);
            }
            else {
                return null;
            }
        }
        return (this.tree$.value
            .map((node) => getNodeById(node, id))
            .filter(Boolean)[0] || null);
    }
    async toNode(parent, item, depth = 0, onExpand = this.expandNode.bind(this), onCollapse = this.collapseNode.bind(this), onSelect = this.selectNode.bind(this), onDeselect = this.deselectNode.bind(this)) {
        return Node.ToNode(parent, item, depth, onExpand, onCollapse, this.toDisplay, this.getIcon, this.getType, onSelect, onDeselect, this.hasDetails, this.getStyle, this.nodeParameters);
    }
    collapseNode(node, options) {
        if (!options?.onlySelf) {
            // required to sync the expanstion state with the tree control
            // if the collpase is trigged by node.colapse this state is not
            // sync with the tree control
            this.treeControl.expansionModel.deselect(node);
        }
        this.expanded.deselect(node.id);
        if (!options?.quite) {
            this.tree$.next(this.tree$.value);
        }
        return Promise.resolve();
    }
    /**
     * Converts the tree structure into a list.
     *
     * @param tree
     * @param all true - include nodes children that are not expanded
     */
    flatTree(tree, all = false) {
        return flatTree(tree, all);
    }
    destroy() {
        super.destroy();
        // TODO : add subscription handler to BaseDataSource
        if (this._expandedLocalStorageSubscription) {
            this._expandedLocalStorageSubscription.unsubscribe();
        }
        if (this._selectedLocalStorageSubscription) {
            this._selectedLocalStorageSubscription.unsubscribe();
        }
    }
    refreshMatchFilter() {
        this._refreshMatchFilter.next();
    }
    async refresh() {
        console.log('selected', this.selected.selected.map((node) => node.id));
        console.log('expanded', this.expanded.selected.slice());
        await this.getTreeRoot({ cache: false });
        // refresh all expanded nodes;
        // const loadExpandedNodes = async (children: ReadonlyArray<Node<Data>>) => {
        //   for (const child of children) {
        //     if (this.expanded.isSelected(child.id)) {
        //       // call the node method to ensure that the expanded property of
        //       // Node is set.
        //       await child.expand();
        //     }
        //
        //     if (child.hasChildren) {
        //       await loadExpandedNodes(child.children);
        //     }
        //   }
        // };
        //
        // await Promise.all(
        //   rootNodes
        //   .filter((node) => node.hasChildren)
        //   .map((node) => loadExpandedNodes(node.children)),
        // );
        // console.log('selected', this.selected.selected.map((node) => node.id));
        //
        // const preSelected = this.selected.selected.slice();
        // this.selected.clear();
        // preSelected.forEach(node => {
        //   node?.select();
        // });
        // const selected: Array<Node<Data>> = this.selected.selected
        // .map((node) => this.getNodeById(node.id))
        // .filter(Boolean) as any;
        // this.selected.clear();
        // this.selected.select(...selected);
    }
    reset() {
        this.selected.clear();
        this.expanded.clear();
        return this.getTreeRoot();
    }
    setGetStyle(getStyle = this.getStyle) {
        this.getStyle = getStyle;
    }
    setGetType(getType = this.getType) {
        this.getType = getType;
    }
    /**
     * recall the getStyle, getIcon and toDisplay methods
     * and update the node objects
     */
    updateNodes() {
        this._data$.value.forEach(node => {
            node.style = this.getStyle(node.item);
            node.icon = coerceArray(this.getIcon(node.item));
            node.display = this.toDisplay(node.item);
        });
        this._data$.next(this._data$.value);
    }
    _connect(collectionViewer) {
        this.init();
        this.treeControl.expansionModel.changed.pipe(tap(async (change) => {
            if (isSelectionChange(change)) {
                const promiseList = [];
                if (change.added) {
                    promiseList.push(...change.added.map((node) => node.expand({ onlySelf: true, quite: true })));
                }
                if (change.removed) {
                    promiseList.push(...change.removed
                        .slice()
                        .reverse()
                        .map((node) => node.collapse({ onlySelf: true, quite: true })));
                }
                await Promise.all(promiseList);
                this.tree$.next(this.tree$.value);
            }
        })).subscribe();
        let loadRoot = Promise.resolve();
        if (this.tree$.value.length === 0) {
            loadRoot = this.getTreeRoot();
        }
        let autoRefreshExecuted = false;
        return from(loadRoot).pipe(
        // tap((rootNodes) => {
        //   if (rootNodes) {
        //     const promises: Promise<any>[] = [];
        //     if (this.metadata.selectMultiple) {
        //       // if (!this.selected.hasValue()) {
        //       //   promises.push(
        //       //     ...rootNodes
        //       //     .filter((node) => node.hasDetails)
        //       //     .map((node) => node.select()),
        //       //   );
        //       // }
        //       if (!this.expanded.hasValue()) {
        //         promises.push(
        //           ...rootNodes
        //           .filter((node) => node.hasChildren)
        //           .map((node) => node.expand()),
        //         );
        //       }
        //     } else if (rootNodes.length) {
        //       const rootNode = rootNodes[0];
        //       // if (!this.selected.hasValue()) {
        //       //   // TODO : rename hasDetails to isSelectable
        //       //   if (rootNode.hasDetails) {
        //       //     promises.push(rootNode.select());
        //       //   }
        //       // }
        //       if (!this.expanded.hasValue()) {
        //         promises.push(rootNode.expand());
        //       }
        //     }
        //     return Promise.all(promises);
        //   }
        //   return Promise.resolve();
        // }),
        switchMap(() => merge(collectionViewer.viewChange, this._data$).pipe(map(() => this._data$.value))), switchMap(nodeList => this._refreshMatchFilter.pipe(startWith(null), map(() => nodeList.filter(node => this.matchFilter(node))))), tap(() => {
            if (this._preSelected.length) {
                console.log('restore selected', this._preSelected);
                this.selected.clear();
                const nodes = this._preSelected.map((id) => this.getNodeById(id));
                this._preSelected = [];
                nodes.forEach(node => {
                    node?.select();
                });
            }
        }), tap(() => {
            if (this.metadata.autoRefreshWithoutCache) {
                if (!autoRefreshExecuted) {
                    autoRefreshExecuted = true;
                    console.log('auto refresh');
                    this.refresh();
                }
            }
        }));
    }
    initSelected() {
        const key = joinPath('rxap/tree', this.id, 'selected');
        if (this.metadata['cacheSelected']) {
            if (localStorage.getItem(key)) {
                try {
                    this._preSelected = JSON.parse(localStorage.getItem(key));
                }
                catch (e) {
                    console.error('parse expanded tree data source nodes failed');
                }
            }
        }
        this.selected = new SelectionModel(!!this.metadata.selectMultiple, []);
        if (this.metadata['cacheSelected']) {
            this._selectedLocalStorageSubscription = this.selected.changed
                .pipe(tap(() => localStorage.setItem(key, JSON.stringify(this.selected.selected.map((s) => s.id)))))
                .subscribe();
        }
    }
    initExpanded() {
        const key = joinPath('rxap/tree', this.id, 'expanded');
        let expanded = [];
        if (this.metadata['cacheExpanded']) {
            if (localStorage.getItem(key)) {
                try {
                    expanded = JSON.parse(localStorage.getItem(key));
                }
                catch (e) {
                    console.error('parse expanded tree data source nodes failed');
                }
            }
        }
        this.expanded = new SelectionModel(this.metadata.expandMultiple !== false, expanded);
        if (this.metadata['cacheExpanded']) {
            this._expandedLocalStorageSubscription = this.expanded.changed
                .pipe(tap(() => localStorage.setItem(key, JSON.stringify(this.expanded.selected))))
                .subscribe();
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeDataSource, deps: [{ token: RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD }, { token: RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD, optional: true }, { token: RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD, optional: true }, { token: RXAP_DATA_SOURCE_METADATA, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeDataSource }); }
};
__decorate([
    Required,
    __metadata("design:type", FlatTreeControl)
], TreeDataSource.prototype, "treeControl", void 0);
TreeDataSource = __decorate([
    RxapDataSource('tree'),
    __metadata("design:paramtypes", [Object, Object, Object, Object])
], TreeDataSource);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeDataSource, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_DATA_SOURCE_METADATA]
                }] }], propDecorators: { treeControl: [] } });

class TreeComponent {
    constructor(viewContainerRef, cdr, contentEditableMethod, renderer, elementRef, searchForm) {
        this.viewContainerRef = viewContainerRef;
        this.cdr = cdr;
        this.renderer = renderer;
        this.elementRef = elementRef;
        this.searchForm = searchForm;
        this.multiple = false;
        this.hideLeafIcon = false;
        this.details = new EventEmitter();
        this.dividerOffset = '256px';
        this.portal = null;
        this.showTreeNavigation = signal(true);
        /**
         * Indicates that the divider is moved with mouse down
         * @private
         */
        this._moveDivider = false;
        /**
         * Holds the current tree container width.
         * If null the move divider feature was not yet used and the initial
         * container width is not calculated
         * @private
         */
        this._treeContainerWidth = null;
        this.getLevel = (node) => node.depth;
        this.isExpandable = (node) => node.hasChildren;
        this.hasChild = (_, nodeData) => nodeData.hasChildren;
        this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable);
        this.contentEditableMethod = contentEditableMethod;
    }
    get nodeDisplayEditable() {
        return !!this.contentEditableMethod;
    }
    get cacheId() {
        return ['rxap', 'tree', this.id].join('/');
    }
    ngOnInit() {
        this.dataSource.searchForm = this.searchForm;
        this.dataSource.setTreeControl(this.treeControl);
        this.dataSource.setToDisplay(this.toDisplay);
        this.dataSource.setGetIcon(this.getIcon);
        this.dataSource.setHasDetails(this.hasDetails);
        this.dataSource.setGetStyle(this.getStyle);
        this.dataSource.setGetType(this.getType);
        this.multiple = this.dataSource.metadata.selectMultiple ?? this.multiple;
        if (this.dataSource.selected.hasValue()) {
            this.dataSource.selected.selected.forEach((node) => this.openDetails(node));
        }
        const cachedOffset = localStorage.getItem(this.cacheId);
        if (cachedOffset && cachedOffset.match(/^(\d+\.)?\d+px$/)) {
            this.setDividerOffset(cachedOffset);
        }
        else if (isDevMode()) {
            console.log('Divider offset cache is not available or invalid: ' + cachedOffset);
        }
    }
    ngAfterContentInit() {
        this.dataSource.selected.changed
            .pipe(map(($event) => $event.source.selected), startWith(this.dataSource.selected.selected), tap((selected) => selected.forEach((node) => this.openDetails(node))))
            .subscribe();
    }
    openDetails(node) {
        if (this.content) {
            this.portal = new TemplatePortal(this.content.template, this.viewContainerRef, {
                $implicit: node.item,
                node,
            });
            this.cdr.markForCheck();
        }
        this.details.emit(node.item);
    }
    onContentEditableChange(value, node) {
        return this.contentEditableMethod?.call(value, node.item, node);
    }
    onMousedown() {
        this._moveDivider = true;
    }
    onMouseup() {
        this._moveDivider = false;
    }
    onMousemove($event) {
        if (this._moveDivider) {
            if (!this._treeContainerWidth) {
                this._treeContainerWidth = this.treeContainer.nativeElement.clientWidth;
            }
            const rect = this.elementRef.nativeElement.getBoundingClientRect();
            this._treeContainerWidth = Math.min(Math.max($event.clientX - (rect.left + 12), 128), rect.right - rect.left - 128);
            const offset = this._treeContainerWidth + 'px';
            this.setDividerOffset(offset);
        }
    }
    toggleTreeNavigation() {
        this.showTreeNavigation.update((value) => !value);
    }
    setDividerOffset(offset) {
        this.dividerOffset = offset;
        this.renderer.setStyle(this.treeContainer.nativeElement, 'max-width', offset);
        this.renderer.setStyle(this.treeContainer.nativeElement, 'min-width', offset);
        this.renderer.setStyle(this.treeContainer.nativeElement, 'flex-basis', offset);
        localStorage.setItem(this.cacheId, offset);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeComponent, deps: [{ token: ViewContainerRef }, { token: ChangeDetectorRef }, { token: RXAP_TREE_CONTENT_EDITABLE_METHOD, optional: true }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: RXAP_FORM_DEFINITION, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.0.6", type: TreeComponent, isStandalone: true, selector: "rxap-tree", inputs: { dataSource: "dataSource", contentEditableMethod: "contentEditableMethod", toDisplay: "toDisplay", getIcon: "getIcon", getType: "getType", getStyle: "getStyle", multiple: "multiple", hasDetails: "hasDetails", hideLeafIcon: "hideLeafIcon", id: "id", dividerOffset: "dividerOffset" }, outputs: { details: "details" }, host: { listeners: { "mouseup": "onMouseup()", "mousemove": "onMousemove($event)" } }, queries: [{ propertyName: "content", first: true, predicate: TreeContentDirective, descendants: true, static: true }], viewQueries: [{ propertyName: "treeContainer", first: true, predicate: ["treeContainer"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"grow flex flex-col gap-2 justify-start items-start\">\n  <button (click)=\"toggleTreeNavigation()\" class=\"!justify-start\" mat-button type=\"button\">\n    <span class=\"flex flex-row justify-start items-center gap-6\">\n      <ng-template [ngIf]=\"showTreeNavigation()\">\n        <mat-icon>arrow_back</mat-icon>\n        <span i18n>Hide tree navigation</span>\n      </ng-template>\n      <ng-template [ngIf]=\"!showTreeNavigation()\">\n        <mat-icon>arrow_forward</mat-icon>\n        <span i18n>Show tree navigation</span>\n      </ng-template>\n    </span>\n  </button>\n  <div class=\"flex flex-row grow w-full\">\n    <div #treeContainer [ngClass]=\"{ 'hidden': !showTreeNavigation() }\"\n         [ngStyle]=\"{ maxWidth: dividerOffset, minWidth: dividerOffset, flexBasis: dividerOffset }\"\n         class=\"w-fit grow-0 overflow-y-auto\">\n      <mat-progress-bar *ngIf=\"!dataSource || (dataSource.loading$ | async)\" mode=\"indeterminate\"></mat-progress-bar>\n      <ng-content select=\"[searchHeader]\"></ng-content>\n      <mat-tree *ngIf=\"dataSource; else loading\" [dataSource]=\"dataSource\" [treeControl]=\"treeControl\">\n\n        <!-- Node without children -->\n        <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodePadding>\n          <div class=\"flex flex-row justify-start items-center gap-2\">\n            <mat-icon [ngClass]=\"{ 'hidden': hideLeafIcon }\">subdirectory_arrow_right</mat-icon>\n            <ng-container *ngIf=\"node.icon?.length\">\n              <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n            </ng-container>\n            <ng-template [ngIf]=\"multiple\">\n              <mat-checkbox\n                (change)=\"node.toggleSelect()\"\n                [checked]=\"node.selected\"\n                [disabled]=\"!node.hasDetails\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\">\n                {{ node.display }}\n              </mat-checkbox>\n            </ng-template>\n            <ng-template [ngIf]=\"!multiple\">\n              <button\n                (click)=\"node.select()\"\n                [color]=\"node.selected ? 'primary' : undefined\"\n                [disabled]=\"!node.hasDetails\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                mat-button\n                type=\"button\"\n              >\n                {{ node.display }}\n              </button>\n            </ng-template>\n          </div>\n        </mat-tree-node>\n\n        <!-- Node with children -->\n        <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" matTreeNodePadding>\n          <button [attr.aria-label]=\"'toggle ' + node.filename\" mat-icon-button matTreeNodeToggle type=\"button\">\n            <mat-icon class=\"mat-icon-rtl-mirror\">\n              {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n            </mat-icon>\n          </button>\n          <div class=\"flex flex-row justify-start items-center gap-2\">\n            <ng-container *ngIf=\"node.icon?.length else withoutIcon\">\n              <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n            </ng-container>\n            <ng-template #withoutIcon>\n              <div></div>\n            </ng-template>\n            <ng-template [ngIfElse]=\"withoutDetails\" [ngIf]=\"node.hasDetails\">\n              <button\n                (click)=\"node.select()\"\n                [color]=\"node.selected ? 'primary' : undefined\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                mat-button\n                type=\"button\"\n              >\n                {{ node.display }}\n              </button>\n            </ng-template>\n            <ng-template #withoutDetails>\n              <span\n                (change)=\"onContentEditableChange($event, node)\"\n                [disabled]=\"!nodeDisplayEditable\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                rxapContenteditable>\n                {{ node.display }}\n              </span>\n            </ng-template>\n            <mat-progress-spinner\n              *ngIf=\"node.isLoading$ | async\"\n              [diameter]=\"16\"\n              class=\"grow-0 pl-4\"\n              mode=\"indeterminate\"\n            ></mat-progress-spinner>\n          </div>\n        </mat-tree-node>\n\n      </mat-tree>\n    </div>\n\n    <div (mousedown)=\"onMousedown()\" *ngIf=\"showTreeNavigation()\"\n         class=\"divider cursor-ew-resize px-3 grow-0\">\n      <mat-divider [vertical]=\"true\" class=\"h-full\"></mat-divider>\n    </div>\n\n    <div class=\"grow\">\n      <ng-container *ngIf=\"portal\">\n        <ng-template [cdkPortalOutlet]=\"portal\"></ng-template>\n      </ng-container>\n      <ng-content></ng-content>\n    </div>\n  </div>\n</div>\n\n<ng-template #loading>Load data source</ng-template>\n", styles: [""], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "ngmodule", type: MatTreeModule }, { kind: "directive", type: i2.MatTreeNodeDef, selector: "[matTreeNodeDef]", inputs: ["matTreeNodeDefWhen", "matTreeNode"] }, { kind: "directive", type: i2.MatTreeNodePadding, selector: "[matTreeNodePadding]", inputs: ["matTreeNodePadding", "matTreeNodePaddingIndent"] }, { kind: "directive", type: i2.MatTreeNodeToggle, selector: "[matTreeNodeToggle]", inputs: ["matTreeNodeToggleRecursive"] }, { kind: "component", type: i2.MatTree, selector: "mat-tree", exportAs: ["matTree"] }, { kind: "directive", type: i2.MatTreeNode, selector: "mat-tree-node", inputs: ["disabled", "tabIndex"], exportAs: ["matTreeNode"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: IconDirective, selector: "mat-icon[rxapIcon]", inputs: ["rxapIcon"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i4.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.MatButton, selector: "    button[mat-button], button[mat-raised-button], button[mat-flat-button],    button[mat-stroked-button]  ", exportAs: ["matButton"] }, { kind: "component", type: i5.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "directive", type: ContenteditableDirective, selector: "[rxapContenteditable]", inputs: ["rxapContenteditable", "disabled"], outputs: ["change"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i6.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i7.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i8.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
__decorate([
    DebounceCall(100),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Node]),
    __metadata("design:returntype", void 0)
], TreeComponent.prototype, "openDetails", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.6", ngImport: i0, type: TreeComponent, decorators: [{
            type: Component,
            args: [{ selector: 'rxap-tree', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
                        NgStyle,
                        NgIf,
                        MatProgressBarModule,
                        MatTreeModule,
                        MatIconModule,
                        IconDirective,
                        MatCheckboxModule,
                        MatButtonModule,
                        ContenteditableDirective,
                        MatProgressSpinnerModule,
                        MatDividerModule,
                        PortalModule,
                        AsyncPipe,
                        NgClass,
                        NgForOf,
                    ], template: "<div class=\"grow flex flex-col gap-2 justify-start items-start\">\n  <button (click)=\"toggleTreeNavigation()\" class=\"!justify-start\" mat-button type=\"button\">\n    <span class=\"flex flex-row justify-start items-center gap-6\">\n      <ng-template [ngIf]=\"showTreeNavigation()\">\n        <mat-icon>arrow_back</mat-icon>\n        <span i18n>Hide tree navigation</span>\n      </ng-template>\n      <ng-template [ngIf]=\"!showTreeNavigation()\">\n        <mat-icon>arrow_forward</mat-icon>\n        <span i18n>Show tree navigation</span>\n      </ng-template>\n    </span>\n  </button>\n  <div class=\"flex flex-row grow w-full\">\n    <div #treeContainer [ngClass]=\"{ 'hidden': !showTreeNavigation() }\"\n         [ngStyle]=\"{ maxWidth: dividerOffset, minWidth: dividerOffset, flexBasis: dividerOffset }\"\n         class=\"w-fit grow-0 overflow-y-auto\">\n      <mat-progress-bar *ngIf=\"!dataSource || (dataSource.loading$ | async)\" mode=\"indeterminate\"></mat-progress-bar>\n      <ng-content select=\"[searchHeader]\"></ng-content>\n      <mat-tree *ngIf=\"dataSource; else loading\" [dataSource]=\"dataSource\" [treeControl]=\"treeControl\">\n\n        <!-- Node without children -->\n        <mat-tree-node *matTreeNodeDef=\"let node\" matTreeNodePadding>\n          <div class=\"flex flex-row justify-start items-center gap-2\">\n            <mat-icon [ngClass]=\"{ 'hidden': hideLeafIcon }\">subdirectory_arrow_right</mat-icon>\n            <ng-container *ngIf=\"node.icon?.length\">\n              <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n            </ng-container>\n            <ng-template [ngIf]=\"multiple\">\n              <mat-checkbox\n                (change)=\"node.toggleSelect()\"\n                [checked]=\"node.selected\"\n                [disabled]=\"!node.hasDetails\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\">\n                {{ node.display }}\n              </mat-checkbox>\n            </ng-template>\n            <ng-template [ngIf]=\"!multiple\">\n              <button\n                (click)=\"node.select()\"\n                [color]=\"node.selected ? 'primary' : undefined\"\n                [disabled]=\"!node.hasDetails\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                mat-button\n                type=\"button\"\n              >\n                {{ node.display }}\n              </button>\n            </ng-template>\n          </div>\n        </mat-tree-node>\n\n        <!-- Node with children -->\n        <mat-tree-node *matTreeNodeDef=\"let node; when: hasChild\" matTreeNodePadding>\n          <button [attr.aria-label]=\"'toggle ' + node.filename\" mat-icon-button matTreeNodeToggle type=\"button\">\n            <mat-icon class=\"mat-icon-rtl-mirror\">\n              {{ treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right' }}\n            </mat-icon>\n          </button>\n          <div class=\"flex flex-row justify-start items-center gap-2\">\n            <ng-container *ngIf=\"node.icon?.length else withoutIcon\">\n              <mat-icon *ngFor=\"let icon of node.icon\" [rxapIcon]=\"$any(icon)\"></mat-icon>\n            </ng-container>\n            <ng-template #withoutIcon>\n              <div></div>\n            </ng-template>\n            <ng-template [ngIfElse]=\"withoutDetails\" [ngIf]=\"node.hasDetails\">\n              <button\n                (click)=\"node.select()\"\n                [color]=\"node.selected ? 'primary' : undefined\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                mat-button\n                type=\"button\"\n              >\n                {{ node.display }}\n              </button>\n            </ng-template>\n            <ng-template #withoutDetails>\n              <span\n                (change)=\"onContentEditableChange($event, node)\"\n                [disabled]=\"!nodeDisplayEditable\"\n                [ngStyle]=\"node.style\"\n                class=\"grow-0 truncate\"\n                rxapContenteditable>\n                {{ node.display }}\n              </span>\n            </ng-template>\n            <mat-progress-spinner\n              *ngIf=\"node.isLoading$ | async\"\n              [diameter]=\"16\"\n              class=\"grow-0 pl-4\"\n              mode=\"indeterminate\"\n            ></mat-progress-spinner>\n          </div>\n        </mat-tree-node>\n\n      </mat-tree>\n    </div>\n\n    <div (mousedown)=\"onMousedown()\" *ngIf=\"showTreeNavigation()\"\n         class=\"divider cursor-ew-resize px-3 grow-0\">\n      <mat-divider [vertical]=\"true\" class=\"h-full\"></mat-divider>\n    </div>\n\n    <div class=\"grow\">\n      <ng-container *ngIf=\"portal\">\n        <ng-template [cdkPortalOutlet]=\"portal\"></ng-template>\n      </ng-container>\n      <ng-content></ng-content>\n    </div>\n  </div>\n</div>\n\n<ng-template #loading>Load data source</ng-template>\n" }]
        }], ctorParameters: () => [{ type: i0.ViewContainerRef, decorators: [{
                    type: Inject,
                    args: [ViewContainerRef]
                }] }, { type: i0.ChangeDetectorRef, decorators: [{
                    type: Inject,
                    args: [ChangeDetectorRef]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TREE_CONTENT_EDITABLE_METHOD]
                }] }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: SearchForm, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_FORM_DEFINITION]
                }] }], propDecorators: { dataSource: [{
                type: Input,
                args: [{ required: true }]
            }], contentEditableMethod: [{
                type: Input
            }], toDisplay: [{
                type: Input
            }], getIcon: [{
                type: Input
            }], getType: [{
                type: Input
            }], getStyle: [{
                type: Input
            }], multiple: [{
                type: Input
            }], hasDetails: [{
                type: Input
            }], content: [{
                type: ContentChild,
                args: [TreeContentDirective, { static: true }]
            }], hideLeafIcon: [{
                type: Input
            }], id: [{
                type: Input
            }], details: [{
                type: Output
            }], dividerOffset: [{
                type: Input
            }], treeContainer: [{
                type: ViewChild,
                args: ['treeContainer', { static: true }]
            }], openDetails: [], onMouseup: [{
                type: HostListener,
                args: ['mouseup']
            }], onMousemove: [{
                type: HostListener,
                args: ['mousemove', ['$event']]
            }] } });

// region 
// endregion

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

export { DefaultTreeApplyFilterMethod, FormFactory, RXAP_TREE_CONTENT_EDITABLE_METHOD, RXAP_TREE_DATA_SOURCE_APPLY_FILTER_METHOD, RXAP_TREE_DATA_SOURCE_CHILDREN_REMOTE_METHOD, RXAP_TREE_DATA_SOURCE_ROOT_REMOTE_METHOD, SearchForm, SearchFormProviders, TreeComponent, TreeContentDirective, TreeDataSource, flatTree, isSelectionChange };
//# sourceMappingURL=rxap-tree.mjs.map
