import * as LayoutRedux from '../../Redux/ActionsReducers/LayoutRedux';
import * as ModuleConstants from '../../Utilities/Constants/ModuleConstants';
import ArrayExtensions, { areArraysEqual } from '../../Utilities/Extensions/ArrayExtensions';
import StringExtensions from '../../Utilities/Extensions/StringExtensions';
import { COLUMN_FILTER_WINDOW } from '../../View/Components/Popups/WindowPopups/windowFactory';
import { ApiBase } from '../Implementation/ApiBase';
import { AdaptableFilterHandler } from '../../agGrid/AdaptableFilterHandler';
export class ColumnFilterInternalApi extends ApiBase {
    /**
     * Returns Predicate Definition for given Column and Quick Filter shortcut
     * @param shortcut Quick Filter shortcut to lookup
     * @param column AdapTable Column being Filtered
     */
    findPredicateDefByShortcut(shortcut, column) {
        return this.getColumnFilterApi()
            .getFilterPredicateDefsForColumn(column)
            .find((predicateDef) => this.getPredicateDefShortcuts(predicateDef)?.includes(shortcut));
    }
    /**
     * Creates an Equality Filter based on given Grid Cells
     * @param gridCells cells to create Filter for
     */
    createValuesColumnFilterForCells(gridCells) {
        if (!gridCells) {
            return null;
        }
        const filter = {
            ColumnId: gridCells[0].column.columnId,
            Predicates: [
                {
                    PredicateId: 'In',
                    Inputs: [...new Set(gridCells.map((gc) => gc.displayValue))],
                },
            ],
        };
        const [savedFilter] = this.getColumnFilterApi().setColumnFilters([filter]) || [null];
        return savedFilter;
    }
    /**
     *  Creates an Equality Filter based on given Grid Cell
     * @param gridCell cell to create Filter for
     */
    createEqualityColumnFilterForCell(gridCell) {
        if (!gridCell) {
            return null;
        }
        const column = gridCell.column;
        const filter = {
            ColumnId: column.columnId,
            Predicates: [
                {
                    PredicateId: this.getPredicateApi().internalApi.getEqualityPredicateForDataType(column.dataType),
                    Inputs: [...new Set([gridCell.rawValue])],
                },
            ],
        };
        const [savedFilter] = this.getColumnFilterApi().setColumnFilters([filter]) || [null];
        return savedFilter;
    }
    /**
     * Calls AdapTableQL to evaluate Filter for given Row
     * @param columnFilter Column Filter to use
     * @param node Row Node to evaluate
     */
    evaluateColumnFilter(columnFilter, node) {
        if (!columnFilter.Predicates.length) {
            return true;
        }
        const someInputsAreEmpty = columnFilter.Predicates.some((predicate) => predicate.Inputs?.some((input) => {
            if (typeof input === 'string') {
                return StringExtensions.IsNullOrEmpty(input);
            }
            if (typeof input === 'number') {
                return isNaN(input);
            }
            return !input;
        }));
        if (someInputsAreEmpty) {
            return true;
        }
        const column = this.getColumnApi().getColumnWithColumnId(columnFilter.ColumnId);
        if (!column) {
            return true;
        }
        const gridCell = this.getGridApi().getGridCellFromRowNode(node, columnFilter.ColumnId);
        if (!gridCell) {
            return true;
        }
        let value = gridCell.normalisedValue;
        const predicateDefHandlerContext = {
            value: value,
            oldValue: null,
            displayValue: gridCell.displayValue,
            node,
            column,
            predicatesOperator: columnFilter.PredicatesOperator,
            ...this.getAdaptableInternalApi().buildBaseContext(),
        };
        return this.getPredicateApi().handlePredicates(columnFilter.Predicates, predicateDefHandlerContext, true);
    }
    /**
     * Checks if the filter action should trigger Column Filtering
     *
     * @param action Filtering Action
     */
    shouldNewColumnFilterTriggerColumnFiltering(action) {
        // trigger filter change only:
        // - new -> new filter is active
        // - clear -> previous filters was active
        // - clearAll -> a filter was active
        // - edit -> same => input change
        // - edit -> different & new is active
        // - edit -> different & old was active
        // - set -> new filter is active
        // filter -> suspend changes
        const isNewAndActive = action.type === LayoutRedux.LAYOUT_COLUMN_FILTER_ADD &&
            this.getColumnFilterApi().isColumnFilterActive(action.columnFilter);
        const isClearAndPreviousWasActive = action.type === LayoutRedux.LAYOUT_COLUMN_FILTER_CLEAR &&
            this.getColumnFilterApi().isColumnFilterActiveForColumn(action.columnId);
        const isClearAllAtLeastOneActiveFilter = action.type === LayoutRedux.LAYOUT_COLUMN_FILTER_CLEAR_ALL &&
            this.getColumnFilterApi()
                .getColumnFilters()
                .some((columnFilter) => this.getColumnFilterApi().isColumnFilterActive(columnFilter));
        let isEditTrigger = false;
        if (action.type === LayoutRedux.LAYOUT_COLUMN_FILTER_EDIT) {
            const newFilter = action.columnFilter;
            const previous = this.getColumnFilterApi().getColumnFilterForColumn(newFilter.ColumnId);
            // same filter edit, so always trigger
            if (previous &&
                newFilter &&
                // same predicates
                areArraysEqual(previous.Predicates.map((p) => p.PredicateId), newFilter.Predicates.map((p) => p.PredicateId))) {
                isEditTrigger = true;
                // new filter is active
            }
            else if (this.getColumnFilterApi().isColumnFilterActive(newFilter)) {
                isEditTrigger = true;
            }
            else if (this.getColumnFilterApi().isColumnFilterActive(previous)) {
                // previous filter was active
                isEditTrigger = true;
            }
        }
        const isSetAndActive = action.type === LayoutRedux.LAYOUT_COLUMN_FILTER_SET &&
            this.getColumnFilterApi().isColumnFilterActive(action.columnFilter);
        const isSuspendChanged = [
            LayoutRedux.LAYOUT_COLUMN_FILTER_SUSPEND,
            LayoutRedux.LAYOUT_COLUMN_FILTER_SUSPEND_ALL,
            LayoutRedux.LAYOUT_COLUMN_FILTER_UNSUSPEND,
            LayoutRedux.LAYOUT_COLUMN_FILTER_UNSUSPEND_ALL,
        ].includes(action.type);
        return (isNewAndActive ||
            isClearAndPreviousWasActive ||
            isClearAllAtLeastOneActiveFilter ||
            isEditTrigger ||
            isSetAndActive ||
            isSuspendChanged);
    }
    /**
     * Compares to sets of Column Filters to see if they are identical
     * @param filters1
     * @param filters2
     */
    areColumnFiltersDifferent(oldFilters, newFilters) {
        if (ArrayExtensions.IsNullOrEmpty(oldFilters) && ArrayExtensions.IsNullOrEmpty(newFilters)) {
            return false;
        }
        return !ArrayExtensions.areArraysEqualWithCustomComparator(oldFilters, newFilters, (filter1, filter2) => {
            // Compare basic properties
            if (filter1.ColumnId !== filter2.ColumnId) {
                return false;
            }
            if ((filter1.PredicatesOperator || 'AND') !== (filter2.PredicatesOperator || 'AND')) {
                return false;
            }
            if ((filter1.IsSuspended || false) !== (filter2.IsSuspended || false)) {
                return false;
            }
            // Compare predicates (regardless of order)
            return ArrayExtensions.areArraysEqualWithCustomComparator(filter1.Predicates, filter2.Predicates, (pred1, pred2) => {
                if (pred1.PredicateId !== pred2.PredicateId) {
                    return false;
                }
                // Compare inputs array (might be primitives or objects)
                return ArrayExtensions.areArraysEqualWithCustomComparator(pred1.Inputs, pred2.Inputs, (input1, input2) => {
                    if (input1 === input2) {
                        return true;
                    }
                    if (typeof input1 !== typeof input2) {
                        return false;
                    }
                    // For object inputs, use JSON comparison
                    if (typeof input1 === 'object' && input1 !== null && input2 !== null) {
                        return JSON.stringify(input1) === JSON.stringify(input2);
                    }
                    return input1 === input2;
                });
            });
        });
    }
    getPredicateDefShortcuts(predicateDef) {
        const shortcuts = this.getFilterOptions().columnFilterOptions.quickFilterWildcards;
        let predicateShortcuts = predicateDef.shortcuts;
        const predicateId = predicateDef.id;
        if (shortcuts?.[predicateId] && Array.isArray(shortcuts[predicateId])) {
            predicateShortcuts = shortcuts[predicateId];
        }
        return predicateShortcuts;
    }
    showQuickFilterDropdown(columnId) {
        const hideQuickFilterDropdownFunction = this.getFilterOptions().columnFilterOptions.hideQuickFilterDropdown;
        if (hideQuickFilterDropdownFunction) {
            const column = this.getColumnApi().getColumnWithColumnId(columnId);
            const columnColumnContext = {
                column: column,
                ...this.getAdaptableInternalApi().buildBaseContext(),
            };
            return !hideQuickFilterDropdownFunction(columnColumnContext);
        }
        return true;
    }
    openColumnFilterPopup(columnId, popupProps) {
        this.getAdaptableInternalApi().showPopupWindow({
            id: COLUMN_FILTER_WINDOW,
            title: ModuleConstants.ColumnFilterFriendlyName,
            icon: 'filter',
            factoryId: COLUMN_FILTER_WINDOW,
            popupProps: {
                value: columnId,
                size: {
                    width: 380,
                    height: 450,
                },
                ...popupProps,
            },
        });
    }
    /**
     * First we try to get the cached values from the FilterHandler
     * Only if the FilterHandler is not available we fallback to the GridApi method
     */
    async getColumnFilterValues(options) {
        const filterOptions = this.getOptionsApi().getFilterOptions();
        const newOptions = {
            columnId: options.columnId,
            get currentSearchValue() {
                // we need this in order to keep it lazy
                return options.currentSearchValue;
            },
            previousResult: undefined,
        };
        const columnFilterHandler = this.getAgGridApi().getColumnFilterHandler(options.columnId);
        if (!columnFilterHandler) {
            this.logWarn(`No ColumnFilterHandler found for columnId: ${options.columnId}!`);
            return this.getGridApi().internalApi.getDistinctFilterDisplayValuesForColumn(newOptions);
        }
        if (typeof columnFilterHandler.getFromCacheOrFetchFilterDisplayValues !== 'function') {
            this.logWarn(`ColumnFilterHandler for columnId: ${options.columnId} does not have getFromCacheOrFetchFilterDisplayValues method!`);
            return this.getGridApi().internalApi.getDistinctFilterDisplayValuesForColumn(newOptions);
        }
        if (filterOptions.customInFilterValues) {
            return columnFilterHandler.fetchFilterDisplayValues(newOptions);
        }
        return columnFilterHandler.getFromCacheOrFetchFilterDisplayValues(newOptions);
    }
    shouldManuallyApplyColumnFilter(columnId) {
        const manuallyApplyColumnFilterOpt = this.getOptionsApi().getFilterOptions().columnFilterOptions?.manuallyApplyColumnFilter;
        if (typeof manuallyApplyColumnFilterOpt === 'boolean') {
            return manuallyApplyColumnFilterOpt;
        }
        if (typeof manuallyApplyColumnFilterOpt === 'function') {
            const column = this.getColumnApi().getColumnWithColumnId(columnId);
            const context = {
                column,
                ...this.getAdaptableApi().internalApi.buildBaseContext(),
            };
            return manuallyApplyColumnFilterOpt(context);
        }
        // fallback, should never happen
        return false;
    }
    getAdaptableFilterHandler(columnId) {
        return this.getAgGridApi().getColumnFilterHandler(columnId);
    }
    getAllAdaptableFilterHandlers() {
        const allFilterableColumns = this.getColumnApi().getFilterableColumns();
        const filterHandlers = allFilterableColumns
            .map((column) => this.getAgGridApi().getColumnFilterHandler(column.columnId))
            .filter(Boolean)
            .filter((handler) => handler instanceof AdaptableFilterHandler);
        return filterHandlers;
    }
}
