import { SelectionModel } from '@angular/cdk/collections';
import * as i0 from '@angular/core';
import { InjectionToken, isDevMode, Injectable, Inject, Optional } from '@angular/core';
import { RXAP_DATA_SOURCE_METADATA } from '@rxap/data-source';
import { AbstractTableDataSource, RXAP_TABLE_DATA_SOURCE_PAGINATOR, RXAP_TABLE_DATA_SOURCE_SORT, RXAP_TABLE_DATA_SOURCE_FILTER, RXAP_TABLE_DATA_SOURCE_PARAMETERS } from '@rxap/data-source/table';
import { Node } from '@rxap/data-structure-tree';
import '@rxap/rxjs';
import { equals, joinPath } from '@rxap/utilities';
import * as i1 from 'rxjs';
import { BehaviorSubject, Subject, combineLatest, of, share } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, skip, tap, switchMap, startWith } from 'rxjs/operators';

const RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD = new InjectionToken('rxap/tree/data-source/root-method');
const RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD = new InjectionToken('rxap/tree/data-source/children-method');
class TreeTableDataSource extends AbstractTableDataSource {
    constructor(rootMethod, childrenMethod, paginator = null, sort = null, filter = null, parameters = null, metadata = rootMethod.metadata) {
        super(paginator, sort, filter, parameters, metadata ?? rootMethod.metadata ?? { id: 'tree-table-data-source' });
        this.rootMethod = rootMethod;
        this.childrenMethod = childrenMethod;
        this.tree$ = new BehaviorSubject([]);
        this._data$ = new Subject();
        this._expandedLocalStorageSubscription = null;
        this.tree$
            .pipe(debounceTime(100), map((tree) => tree
            .map((child) => this.flatTree(child))
            .reduce((acc, nodes) => [...acc, ...nodes], [])), map((flatTree) => flatTree.map((node) => ({
            ...node.item,
            __node: node,
        }))))
            .subscribe(this._data$);
        this.initExpanded();
    }
    init() {
        if (this._initialised) {
            return;
        }
        super.init();
        this.getTreeRoot();
        // TODO : workaround the handle parameters change
        if (this.parameters) {
            this._subscription = this.parameters
                .pipe(distinctUntilChanged((a, b) => equals(a, b)), skip(1), tap(() => this.refresh()))
                .subscribe();
        }
    }
    async getTreeRoot(event = {}) {
        this.loading$.next(true);
        this.hasError$.disable();
        const root = await this.getRoot(event).catch((error) => {
            this.hasError$.enable();
            throw new Error(`Failed to load root nodes: ${error.message}`);
        });
        let rootNode = [];
        if (Array.isArray(root)) {
            for (const node of root) {
                rootNode.push(await this._toNode(node));
            }
        }
        else {
            rootNode = [await this._toNode(root)];
            if (!rootNode[0].expanded) {
                await rootNode[0].expand();
            }
        }
        this.loading$.next(false);
        this.tree$.next(rootNode);
    }
    async getChildren(node, event) {
        return this.childrenMethod.call({ node, event });
    }
    async getRoot(event) {
        return this.rootMethod.call(event);
    }
    collapseNode(node) {
        this.expanded.deselect(node.id);
        this.tree$.next(this.tree$.value);
        return Promise.resolve();
    }
    async _toNode(row, depth = 0, onExpand = this.expandNode.bind(this), onCollapse = this.collapseNode.bind(this)) {
        const node = await this.toNode(row, depth, onExpand, onCollapse);
        if (this.expanded.isSelected(node.id) && !node.expanded) {
            await node
                .expand()
                .then(() => console.debug(`Restore expand for node '${node.id}' SUCCESSFULLY`))
                .catch(() => console.debug(`Restore expand for node '${node.id}' FAILED`));
        }
        for (const child of node.children) {
            if (this.expanded.isSelected(child.id) && !child.expanded) {
                await child
                    .expand()
                    .then(() => console.debug(`Restore expand for child node '${child.id}' SUCCESSFULLY`))
                    .catch(() => console.debug(`Restore expand for child node '${child.id}' FAILED`));
            }
        }
        return node;
    }
    async expandNode(node) {
        if (node.item.hasChildren && !node.item.children?.length) {
            // TODO : get treeTableEvent from paginator, sort and filter
            const children = await this.getChildren(node, {}).catch((error) => {
                this.hasError$.enable();
                throw new Error(`Failed to load children nodes for '${node.id}': ${error.message}`);
            });
            node.item.children = children;
            const addChildren = [];
            for (const child of children) {
                const addChild = await this._toNode(child, node.depth +
                    1, node.onExpand, node.onCollapse);
                addChildren.push(addChild);
            }
            node.addChildren(addChildren);
        }
        this.expanded.select(node.id);
        this.tree$.next(this.tree$.value);
    }
    toNode(row, depth = 0, onExpand = this.expandNode.bind(this), onCollapse = this.collapseNode.bind(this)) {
        return Node.ToNode(null, row, depth, onExpand, onCollapse);
    }
    ngOnDestroy() {
        super.ngOnDestroy();
        this._expandedLocalStorageSubscription?.unsubscribe();
        this._subscription?.unsubscribe();
    }
    flatTree(tree) {
        function flat(acc, list) {
            return [...acc, ...list];
        }
        function flatTree(node) {
            if (!Array.isArray(node.children)) {
                if (isDevMode()) {
                    console.log(node);
                }
                throw new Error('Node has not defined children');
            }
            if (node.expanded) {
                return [
                    node,
                    ...node.children.map((child) => flatTree(child)).reduce(flat, []),
                ];
            }
            else {
                return [node];
            }
        }
        return flatTree(tree);
    }
    retry() {
        return this.getTreeRoot();
    }
    refresh() {
        return this.getTreeRoot();
    }
    isMatchingFilter(item, filterList) {
        for (const filter of filterList) {
            const value = item[filter.column];
            if (value === filter.filter) {
                return true;
            }
            if (typeof value === 'string' && typeof filter.filter === 'string') {
                return value.toLowerCase().includes(filter.filter.toLowerCase());
            }
        }
        return false;
    }
    hasMatchingNodesByFilter(nodeList, filterList) {
        if (!filterList.length) {
            return true;
        }
        return nodeList.some((node) => {
            if (this.isMatchingFilter(node.item, filterList)) {
                return true;
            }
            if (!node.hasChildren) {
                // If item has no children and doesn't pass the filter, remove it
                return false;
            }
            return this.hasMatchingNodesByFilter(node.children ?? [], filterList);
        });
    }
    applyFilterBy(data, filter) {
        const filterList = typeof filter === 'string' ?
            [] :
            Object.entries(filter).map(([column, filter]) => ({ column, filter }));
        if (!filterList.length ||
            filterList.every((filter) => !filter.filter && filter.filter !== false && filter.filter !== 0)) {
            return data.slice();
        }
        return data.filter(item => {
            if (this.isMatchingFilter(item, filterList)) {
                return true;
            }
            const hasMatch = this.hasMatchingNodesByFilter(item.__node.children, filterList);
            if (hasMatch) {
                item.__node.expand();
            }
            return hasMatch;
        });
    }
    _connect(viewer) {
        this.init();
        return this._data$.pipe(tap((data) => this.updateTotalLength(data.length)), distinctUntilChanged((a, b) => {
            if (a.length !== b.length) {
                return false;
            }
            if (!equals(a.map(item => item.__node.id), b.map(item => item.__node.id))) {
                return false;
            }
            return equals(a.map(item => item.__node.item).map(item => ({ ...item, children: null })), b.map(item => item.__node.item).map(item => ({ ...item, children: null })));
        }), switchMap((data) => {
            return combineLatest([
                this.paginator?.page?.pipe(startWith({
                    pageIndex: this.paginator.pageIndex,
                    pageSize: this.paginator.pageSize,
                    length: this.paginator.length,
                })) ?? of(null),
                this.sort?.sortChange?.pipe(startWith({
                    active: this.sort?.active,
                    direction: this.sort?.direction,
                })) ?? of(null),
                this.filter?.change.pipe(startWith({})) ?? of(null),
            ]).pipe(map(([page, sort, filter]) => ({
                page,
                sort,
                filter,
            })), debounceTime(500), distinctUntilChanged((a, b) => equals(a, b)), map((event) => {
                const { page, sort, filter } = event;
                let filteredData = data;
                if (filter) {
                    filteredData = this.applyFilterBy(filteredData, filter);
                }
                let sortData = filteredData;
                if (sort) {
                    sortData = this.applySortBy(sortData, sort.active, sort.direction);
                }
                if (page) {
                    return this.applyPagination(sortData, page.pageSize, page.pageIndex);
                }
                return sortData;
            }));
        }), share());
    }
    initExpanded() {
        const key = joinPath('rxap/table-system/tree-table', this.id, 'expanded');
        let expanded = [];
        if (localStorage.getItem(key)) {
            try {
                expanded = JSON.parse(localStorage.getItem(key));
            }
            catch (e) {
                console.debug('parse expanded tree table data source nodes failed');
            }
        }
        this.expanded = new SelectionModel(true, expanded);
        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: "19.0.3", ngImport: i0, type: TreeTableDataSource, deps: [{ token: RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD }, { token: RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD }, { token: RXAP_TABLE_DATA_SOURCE_PAGINATOR, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_SORT, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_FILTER, optional: true }, { token: RXAP_TABLE_DATA_SOURCE_PARAMETERS, optional: true }, { token: RXAP_DATA_SOURCE_METADATA, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: TreeTableDataSource }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: TreeTableDataSource, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: undefined, decorators: [{
                    type: Inject,
                    args: [RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD]
                }] }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TABLE_DATA_SOURCE_PAGINATOR]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TABLE_DATA_SOURCE_SORT]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TABLE_DATA_SOURCE_FILTER]
                }] }, { type: i1.Observable, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_TABLE_DATA_SOURCE_PARAMETERS]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_DATA_SOURCE_METADATA]
                }] }] });

// region 
// endregion

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

export { RXAP_TREE_TABLE_DATA_SOURCE_CHILDREN_METHOD, RXAP_TREE_TABLE_DATA_SOURCE_ROOT_METHOD, TreeTableDataSource };
//# sourceMappingURL=rxap-data-source-table-tree.mjs.map
