// (C) 2021-2026 GoodData Corporation
import { createSelector } from "@reduxjs/toolkit";
import { compact, isEmpty, isEqual, keyBy, sortBy } from "lodash-es";
import { invariant } from "ts-invariant";
import { areObjRefsEqual, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemTitle, dashboardFilterLocalIdentifier, getAttributeElementsItems, idRef, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilterWithDimension, isDashboardMeasureValueFilter, isDashboardTextAttributeFilter, isObjRef, uriRef, } from "@gooddata/sdk-model";
import { newDisplayFormMap } from "../../../../_staging/metadata/objRefMap.js";
import { createMemoizedSelector } from "../../_infra/selectors.js";
import { selectSupportsCircularDependencyInFilters } from "../../backendCapabilities/backendCapabilitiesSelectors.js";
import { selectEnableImmediateAttributeFilterDisplayAsLabelMigration } from "../../config/configSelectors.js";
import { selectCrossFilteringFiltersLocalIdentifiers } from "../../drill/drillSelectors.js";
import { selectActiveTabLocalIdentifier, selectTabs, selectTabsState } from "../tabsSelectors.js";
import { DEFAULT_TAB_ID } from "../tabsState.js";
import { filterContextInitialState } from "./filterContextState.js";
import { applyFilterContext, getFilterIdentifier } from "./filterContextUtils.js";
const selectSelf = createSelector(selectTabs, selectActiveTabLocalIdentifier, (tabs, activeTabId) => {
    if (!tabs || !activeTabId) {
        return filterContextInitialState;
    }
    const activeTab = tabs.find((tab) => tab.localIdentifier === activeTabId);
    return activeTab?.filterContext ?? filterContextInitialState;
});
const selectTabsArray = createSelector(selectTabs, (tabs) => [...(tabs ?? [])]);
/**
 * Returns filter context state for each tab keyed by identifier.
 *
 * @internal
 */
export const selectFilterContextStatesByTab = createSelector(selectTabsArray, (tabs) => {
    if (!tabs.length) {
        return {};
    }
    return tabs.reduce((acc, tab) => {
        const identifier = tab.localIdentifier ?? DEFAULT_TAB_ID;
        acc[identifier] = tab.filterContext ?? filterContextInitialState;
        return acc;
    }, {});
});
/**
 * Returns applied filter context definitions for each tab keyed by identifier.
 *
 * @internal
 */
export const selectFilterContextDefinitionsByTab = createSelector(selectFilterContextStatesByTab, (statesByTab) => {
    return Object.entries(statesByTab).reduce((acc, [identifier, state]) => {
        acc[identifier] = state.filterContextDefinition;
        return acc;
    }, {});
});
/**
 * Returns filters from each tab keyed by tab identifier.
 * This selector is useful for exporting dashboards with per-tab filters.
 *
 * @internal
 */
export const selectFiltersByTab = createSelector(selectFilterContextDefinitionsByTab, (definitionsByTab) => {
    return Object.entries(definitionsByTab).reduce((acc, [identifier, definition]) => {
        acc[identifier] = definition?.filters ?? [];
        return acc;
    }, {});
});
/**
 * Returns original (persisted) filter context definitions for each tab keyed by identifier.
 *
 * @internal
 */
export const selectOriginalFilterContextDefinitionsByTab = createSelector(selectFilterContextStatesByTab, (statesByTab) => {
    return Object.entries(statesByTab).reduce((acc, [identifier, state]) => {
        acc[identifier] = state.originalFilterContextDefinition;
        return acc;
    }, {});
});
/**
 * Returns original (persisted) filters for each tab keyed by identifier.
 *
 * @internal
 */
export const selectOriginalFilterContextFiltersByTab = createSelector(selectOriginalFilterContextDefinitionsByTab, (definitionsByTab) => {
    return Object.entries(definitionsByTab).reduce((acc, [identifier, definition]) => {
        acc[identifier] = definition?.filters ?? [];
        return acc;
    }, {});
});
/**
 * This selector returns array of filter localIds that have invalid selections.
 *
 * @returns Array of filter localIds with invalid selections.
 *
 * @public
 */
export const selectFiltersWithInvalidSelection = createSelector(selectSelf, (filterContextState) => filterContextState.filtersWithInvalidSelection);
/**
 * This selector returns original (stored) dashboard's filter context definition.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @returns {@link @gooddata/sdk-backend-spi#IFilterContextDefinition} or `undefined` if original filter context definition is not set.
 *
 * @public
 */
export const selectOriginalFilterContextDefinition = createSelector(selectSelf, (filterContextState) => {
    invariant(filterContextState.filterContextDefinition, "attempting to access uninitialized filter context state");
    return filterContextState.originalFilterContextDefinition;
});
/**
 * This selector returns original (stored) dashboard's filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @returns an array of {@link @gooddata/sdk-backend-spi#FilterContextItem} or an empty array.
 *
 * @public
 */
export const selectOriginalFilterContextFilters = createSelector(selectOriginalFilterContextDefinition, (filterContext) => filterContext?.filters ?? []);
/**
 * This selector returns current applied dashboard's filter context definition.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @returns a {@link @gooddata/sdk-backend-spi#IFilterContextDefinition}
 *
 * @public
 */
export const selectFilterContextDefinition = createSelector(selectSelf, (filterContextState) => {
    invariant(filterContextState.filterContextDefinition, "attempting to access uninitialized applied filter context state");
    return filterContextState.filterContextDefinition;
});
/**
 * This selector returns current working dashboard's filter context definition.
 *
 * @remarks
 * It is expected that the selector is called only after the wokring filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @returns a {@link @gooddata/sdk-backend-spi#IFilterContextDefinition}
 *
 * @alpha
 */
export const selectWorkingFilterContextDefinition = createSelector(selectSelf, (state) => {
    invariant(state.filterContextDefinition, "attempting to access uninitialized working filter context state");
    return applyFilterContext(state.filterContextDefinition, state.workingFilterContextDefinition);
});
/**
 * Returns true if working filters and applied filters are same.
 *
 * Attribute filters are considered equal if
 *  - they have same localIdentifier
 *  - they have same elements (order does not matter)
 *  - all other fields are deep equal EXCEPT displayForm
 * We exclude diplayForm becuase of primary display form migration code.
 * Which changes display forms after update.
 *
 * @alpha
 */
export const selectIsWorkingFilterContextChanged = createSelector(selectFilterContextDefinition, selectWorkingFilterContextDefinition, selectEnableImmediateAttributeFilterDisplayAsLabelMigration, (filterContext, workingFilterContext, enableImmediateAttributeFilterDisplayAsLabelMigration) => {
    if (filterContext.filters.length !== workingFilterContext.filters.length) {
        return true;
    }
    const appliedFilters = keyBy(filterContext.filters, getFilterIdentifier);
    const workingFilters = keyBy(workingFilterContext.filters, getFilterIdentifier);
    return !Object.keys(appliedFilters)
        .map((key) => {
        const appliedFilter = appliedFilters[key];
        const workingFilter = workingFilters[key];
        if (isDashboardAttributeFilter(appliedFilter) && isDashboardAttributeFilter(workingFilter)) {
            const { attributeElements: appliedElements, ...appliedFilterWithoutElements } = appliedFilter.attributeFilter;
            const { attributeElements: workingElements, ...workingFilterWithoutElements } = workingFilter.attributeFilter;
            // This code can be removed when enableImmediateAttributeFilterDisplayAsLabelMigration is removed
            const partialAppliedFilter = appliedFilterWithoutElements;
            const partialWorkingFilter = workingFilterWithoutElements;
            if (!enableImmediateAttributeFilterDisplayAsLabelMigration) {
                delete partialAppliedFilter.displayForm;
                delete partialWorkingFilter.displayForm;
            }
            return (isEqual(partialAppliedFilter, partialWorkingFilter) &&
                isEqual(sortBy(getAttributeElementsItems(appliedElements)), sortBy(getAttributeElementsItems(workingElements))));
        }
        // Text filters (arbitrary, match) — straight deep comparison,
        // type change between arbitrary and match is considered a change
        if (isDashboardTextAttributeFilter(appliedFilter) ||
            isDashboardTextAttributeFilter(workingFilter)) {
            return isEqual(appliedFilter, workingFilter);
        }
        // Date filters
        return isEqual(appliedFilter, workingFilter);
    })
        .every((v) => v);
});
/**
 * Selects dashboard's filter context identity.
 *
 * @remarks
 * The identity may be undefined in two circumstances:
 *
 * -  a new, yet unsaved dashboard; the filter context is saved together with the dashboard and after the
 *    save the identity will be known and added
 *
 * -  export of an existing, saved dashboard; during the export the dashboard receives a temporary
 *    filter context that represents values of filters at the time the export was initiated - which may
 *    be different from what is saved in the filter context itself. that temporary context is not
 *    persistent and lives only for that particular export operation.
 *
 * Invocations before initialization lead to invariant errors.
 *
 * @returns a {@link @gooddata/sdk-backend-spi#IDashboardObjectIdentity} or undefined, if the filter context identity is not set.
 *
 * @internal
 */
export const selectFilterContextIdentity = createSelector(selectSelf, (filterContextState) => {
    // this is intentional; want to fail fast when trying to access an optional identity of filter context \
    // but there is actually no filter context initialized for the dashboard
    invariant(filterContextState.filterContextDefinition, "attempting to access uninitialized filter context state");
    return filterContextState.filterContextIdentity;
});
/**
 * Selects list of display form metadata objects referenced by attribute filters.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @returns an array of {@link @gooddata/sdk-backend-spi#IAttributeDisplayFormMetadataObject}
 *
 * @public
 */
export const selectAttributeFilterDisplayForms = createSelector(selectSelf, (filterContextState) => {
    invariant(filterContextState.attributeFilterDisplayForms, "attempting to access uninitialized filter context state");
    return filterContextState.attributeFilterDisplayForms;
});
/**
 * Selects map of display form metadata objects referenced by attribute filters.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @returns a {@link ObjRefMap} of {@link @gooddata/sdk-backend-spi#IAttributeDisplayFormMetadataObject}
 *
 * @internal
 */
export const selectAttributeFilterDisplayFormsMap = createSelector(selectSelf, (filterContextState) => {
    invariant(filterContextState.attributeFilterDisplayForms, "attempting to access uninitialized filter context state");
    return newDisplayFormMap(filterContextState.attributeFilterDisplayForms);
});
/**
 * This selector returns dashboard's applied filter context filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextFilters = createSelector(selectFilterContextDefinition, (filterContext) => filterContext.filters);
/**
 * This selector returns dashboard's working filter context filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectWorkingFilterContextFilters = createSelector(selectWorkingFilterContextDefinition, (filterContext) => filterContext.filters);
/**
 * This selector returns dashboard's applied filter context attribute filters with element-based selection only.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not include arbitrary or match (text) attribute filters.
 * Use {@link selectFilterContextAttributeFilterItems} to get all attribute-based filters.
 *
 * @public
 * @deprecated Use {@link selectFilterContextAttributeFilterItems} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilters = createSelector(selectFilterContextFilters, (filters) => filters.filter(isDashboardAttributeFilter));
/**
 * This selector returns all dashboard's applied filter context attribute-based filters,
 * including element-based, arbitrary, and match filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextAttributeFilterItems = createSelector(selectFilterContextFilters, (filters) => filters.filter(isDashboardAttributeFilterItem));
/**
 * This selector returns dashboard's working filter context attribute filters with element-based selection only.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not include arbitrary or match (text) attribute filters.
 * Use {@link selectWorkingFilterContextAttributeFilterItems} to get all attribute-based filters.
 *
 * @public
 * @deprecated Use {@link selectWorkingFilterContextAttributeFilterItems} to include all attribute filter types.
 */
export const selectWorkingFilterContextAttributeFilters = createSelector(selectWorkingFilterContextFilters, (filters) => filters.filter(isDashboardAttributeFilter));
/**
 * This selector returns all dashboard's working filter context attribute-based filters,
 * including element-based, arbitrary, and match filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectWorkingFilterContextAttributeFilterItems = createSelector(selectWorkingFilterContextFilters, (filters) => filters.filter(isDashboardAttributeFilterItem));
/**
 * This selector returns dashboard's applied filter context date filter.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDateFilter = createSelector(selectFilterContextFilters, (filters) => filters.find(isDashboardCommonDateFilter));
/**
 * This selector returns dashboard's working filter context date filter.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectWorkingFilterContextDateFilter = createSelector(selectWorkingFilterContextFilters, (filters) => filters.find(isDashboardCommonDateFilter));
/**
 * This selector returns dashboard's filter context draggable filters (element-based attribute filters and date filters).
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not include arbitrary or match (text) attribute filters.
 * Use {@link selectFilterContextDraggableFilterItems} to get all draggable filters.
 *
 * @public
 * @deprecated Use {@link selectFilterContextDraggableFilterItems} to include all attribute filter types.
 */
export const selectFilterContextDraggableFilters = createSelector(selectFilterContextFilters, (filters) => filters.filter((f) => isDashboardDateFilterWithDimension(f) || isDashboardAttributeFilter(f)));
/**
 * This selector returns all dashboard's filter context draggable filters,
 * including date filters, all attribute-based filters (element-based, arbitrary, and match),
 * and measure value filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDraggableFilterItems = createSelector(selectFilterContextFilters, (filters) => filters.filter((f) => isDashboardDateFilterWithDimension(f) ||
    isDashboardAttributeFilterItem(f) ||
    isDashboardMeasureValueFilter(f)));
/**
 * This selector returns dashboard's applied filter context measure value filters.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @alpha
 */
export const selectFilterContextMeasureValueFilters = createSelector(selectFilterContextFilters, (filters) => filters.filter(isDashboardMeasureValueFilter));
/**
 * Creates a selector for finding a dashboard measure value filter by its local identifier.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @alpha
 */
export const selectFilterContextMeasureValueFilterByLocalId = createMemoizedSelector((localIdentifier) => createSelector(selectFilterContextMeasureValueFilters, (filters) => filters.find((f) => dashboardFilterLocalIdentifier(f) === localIdentifier)));
/**
 * Creates a selector for finding a working dashboard measure value filter by its local identifier.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @alpha
 */
export const selectWorkingFilterContextMeasureValueFilterByLocalId = createMemoizedSelector((localIdentifier) => createSelector(selectWorkingFilterContextFilters, (filters) => filters
    .filter(isDashboardMeasureValueFilter)
    .find((f) => dashboardFilterLocalIdentifier(f) === localIdentifier)));
/**
 * This selector returns dashboard's applied filter context date filter with dimension specified.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDateFiltersWithDimension = createSelector(selectFilterContextFilters, (filters) => filters.filter(isDashboardDateFilterWithDimension));
/**
 * This selector returns dashboard's working filter context date filter with dimension specified.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectWorkingFilterContextDateFiltersWithDimension = createSelector(selectWorkingFilterContextFilters, (filters) => filters.filter(isDashboardDateFilterWithDimension));
/**
 * Creates a selector for selecting date filter by its dataset {@link @gooddata/sdk-model#ObjRef}.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDateFilterByDataSet = createMemoizedSelector((dataSet) => createSelector(selectFilterContextDateFiltersWithDimension, (dateFilters) => {
    return dateFilters.find((filter) => areObjRefsEqual(filter.dateFilter.dataSet, dataSet));
}));
/**
 * Creates a selector for selecting date filter's index by its dataset {@link @gooddata/sdk-model#ObjRef}.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDateFilterIndexByDataSet = createMemoizedSelector((dataSet) => createSelector(selectFilterContextDateFiltersWithDimension, (dateFilters) => {
    return dateFilters.findIndex((filter) => areObjRefsEqual(filter.dateFilter.dataSet, dataSet));
}));
/**
 * Returns date filters with dimension for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Date filters with dimension for the specified tab
 *
 * @internal
 */
export const selectFilterContextDateFiltersWithDimensionForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFiltersByTab, (filtersByTab) => {
    const filters = filtersByTab[tabLocalIdentifier] ?? [];
    return filters.filter(isDashboardDateFilterWithDimension);
}));
/**
 * Returns the common date filter for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Common date filter for the specified tab, or undefined if not found
 *
 * @internal
 */
export const selectFilterContextDateFilterForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFiltersByTab, (filtersByTab) => {
    const filters = filtersByTab[tabLocalIdentifier] ?? [];
    return filters.find(isDashboardCommonDateFilter);
}));
/**
 * Creates a selector for selecting date filter by its dataset from a specific tab.
 *
 * @param dataSet - Date dataset ref
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Date filter matching the dataset, or undefined if not found
 *
 * @internal
 */
export const selectFilterContextDateFilterByDataSetForTab = createMemoizedSelector((dataSet, tabLocalIdentifier) => createSelector(selectFilterContextDateFiltersWithDimensionForTab(tabLocalIdentifier), (dateFilters) => {
    return dateFilters.find((filter) => areObjRefsEqual(filter.dateFilter.dataSet, dataSet));
}));
/**
 * Creates a selector for selecting draggable filter's index by its ref:
 * dataSet ref for date filters {@link @gooddata/sdk-model#ObjRef}
 * localIdentifier for attribute, text attribute, and measure value filters
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextDraggableFilterIndexByRef = createMemoizedSelector((ref) => createSelector(selectFilterContextFilters, (filters) => {
    return filters.findIndex((filter) => {
        if (isDashboardDateFilterWithDimension(filter) && isObjRef(ref)) {
            return areObjRefsEqual(filter.dateFilter.dataSet, ref);
        }
        if (isDashboardAttributeFilterItem(filter) && typeof ref === "string") {
            return dashboardAttributeFilterItemLocalIdentifier(filter) === ref;
        }
        if (isDashboardMeasureValueFilter(filter) && typeof ref === "string") {
            return dashboardFilterLocalIdentifier(filter) === ref;
        }
        return false;
    });
}));
/**
 * Creates a selector for selecting element-based attribute filter by its displayForm {@link @gooddata/sdk-model#ObjRef}.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not match arbitrary or match (text) attribute filters.
 * Use {@link selectFilterContextAttributeFilterItemByDisplayForm} to match all attribute filter types.
 *
 * @public
 * @deprecated Use {@link selectFilterContextAttributeFilterItemByDisplayForm} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilterByDisplayForm = createMemoizedSelector((displayForm) => createSelector(selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilters, (attributeDisplayFormsMap, attributeFilters) => {
    const df = attributeDisplayFormsMap.get(displayForm);
    if (!df) {
        return undefined;
    }
    // try matching both uri and id in case the type of ref is different from what is in the ref field
    return attributeFilters.find((filter) => areObjRefsEqual(filter.attributeFilter.displayForm, idRef(df.id, "displayForm")) ||
        areObjRefsEqual(filter.attributeFilter.displayForm, uriRef(df.uri)));
}));
/**
 * Creates a selector for selecting any attribute-based filter by its displayForm {@link @gooddata/sdk-model#ObjRef}.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextAttributeFilterItemByDisplayForm = createMemoizedSelector((displayForm) => createSelector(selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilterItems, (attributeDisplayFormsMap, attributeFilters) => {
    const df = attributeDisplayFormsMap.get(displayForm);
    if (!df) {
        return undefined;
    }
    return attributeFilters.find((filter) => {
        const filterDf = dashboardAttributeFilterItemDisplayForm(filter);
        return (areObjRefsEqual(filterDf, idRef(df.id, "displayForm")) ||
            areObjRefsEqual(filterDf, uriRef(df.uri)));
    });
}));
/**
 * Creates a selector for selecting element-based attribute filter by its localId.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not match arbitrary or match (text) attribute filters.
 * Use {@link selectFilterContextAttributeFilterItemByLocalId} to match all attribute filter types.
 *
 * @public
 * @deprecated Use {@link selectFilterContextAttributeFilterItemByLocalId} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilterByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilters, (attributeFilters) => attributeFilters.find((filter) => filter.attributeFilter.localIdentifier === localId)));
/**
 * Creates a selector for selecting any attribute-based filter by its localId.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextAttributeFilterItemByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilterItems, (attributeFilters) => attributeFilters.find((filter) => dashboardAttributeFilterItemLocalIdentifier(filter) === localId)));
/**
 * Returns element-based attribute filters for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Attribute filters for the specified tab
 *
 * @internal
 * @deprecated Use {@link selectFilterContextAttributeFilterItemsForTab} to include all attribute filter types.
 */
export const selectFilterContextAttributeFiltersForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFiltersByTab, (filtersByTab) => {
    const filters = filtersByTab[tabLocalIdentifier] ?? [];
    return filters.filter(isDashboardAttributeFilter);
}));
/**
 * Returns all attribute-based filters (element-based, arbitrary, and match) for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns All attribute-based filters for the specified tab
 *
 * @internal
 */
export const selectFilterContextAttributeFilterItemsForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFiltersByTab, (filtersByTab) => {
    const filters = filtersByTab[tabLocalIdentifier] ?? [];
    return filters.filter(isDashboardAttributeFilterItem);
}));
/**
 * Creates a selector for selecting element-based attribute filter by its display form from a specific tab.
 *
 * @param displayForm - Display form ref
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Attribute filter matching the display form, or undefined if not found
 *
 * @internal
 * @deprecated Use {@link selectFilterContextAttributeFilterItemByDisplayFormForTab} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilterByDisplayFormForTab = createMemoizedSelector((displayForm, tabLocalIdentifier) => createSelector(selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFiltersForTab(tabLocalIdentifier), (attributeDisplayFormsMap, attributeFilters) => {
    const df = attributeDisplayFormsMap.get(displayForm);
    if (!df) {
        return undefined;
    }
    // try matching both uri and id in case the type of ref is different from what is in the ref field
    return attributeFilters.find((filter) => areObjRefsEqual(filter.attributeFilter.displayForm, idRef(df.id, "displayForm")) ||
        areObjRefsEqual(filter.attributeFilter.displayForm, uriRef(df.uri)));
}));
/**
 * Creates a selector for selecting any attribute-based filter by its display form from a specific tab.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @param displayForm - Display form ref
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Attribute filter item matching the display form, or undefined if not found
 *
 * @internal
 */
export const selectFilterContextAttributeFilterItemByDisplayFormForTab = createMemoizedSelector((displayForm, tabLocalIdentifier) => createSelector(selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilterItemsForTab(tabLocalIdentifier), (attributeDisplayFormsMap, attributeFilters) => {
    const df = attributeDisplayFormsMap.get(displayForm);
    if (!df) {
        return undefined;
    }
    return attributeFilters.find((filter) => {
        const filterDf = dashboardAttributeFilterItemDisplayForm(filter);
        return (areObjRefsEqual(filterDf, idRef(df.id, "displayForm")) ||
            areObjRefsEqual(filterDf, uriRef(df.uri)));
    });
}));
/**
 * Creates a selector for selecting element-based attribute filter by its localId from a specific tab.
 *
 * @param localId - Filter local identifier
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Attribute filter matching the local id, or undefined if not found
 *
 * @internal
 * @deprecated Use {@link selectFilterContextAttributeFilterItemByLocalIdForTab} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilterByLocalIdForTab = createMemoizedSelector((localId, tabLocalIdentifier) => createSelector(selectFilterContextAttributeFiltersForTab(tabLocalIdentifier), (attributeFilters) => attributeFilters.find((filter) => filter.attributeFilter.localIdentifier === localId)));
/**
 * Creates a selector for selecting any attribute-based filter by its localId from a specific tab.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @param localId - Filter local identifier
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Attribute filter item matching the local id, or undefined if not found
 *
 * @internal
 */
export const selectFilterContextAttributeFilterItemByLocalIdForTab = createMemoizedSelector((localId, tabLocalIdentifier) => createSelector(selectFilterContextAttributeFilterItemsForTab(tabLocalIdentifier), (attributeFilters) => attributeFilters.find((filter) => dashboardAttributeFilterItemLocalIdentifier(filter) === localId)));
/**
 * Creates a selector for selecting element-based attribute filter index by its localId.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * Note: This selector does not match arbitrary or match (text) attribute filters.
 * Use {@link selectFilterContextAttributeFilterItemIndexByLocalId} to match all attribute filter types.
 *
 * @public
 * @deprecated Use {@link selectFilterContextAttributeFilterItemIndexByLocalId} to include all attribute filter types.
 */
export const selectFilterContextAttributeFilterIndexByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilters, (attributeFilters) => attributeFilters.findIndex((filter) => filter.attributeFilter.localIdentifier === localId)));
/**
 * Creates a selector for selecting any attribute-based filter index by its localId.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectFilterContextAttributeFilterItemIndexByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilterItems, (attributeFilters) => attributeFilters.findIndex((filter) => dashboardAttributeFilterItemLocalIdentifier(filter) === localId)));
/**
 * Creates a selector for selecting all descendants of the attribute filter with given localId.
 *
 * @remarks
 * Invocations before initialization lead to invariant errors.
 * This selector should only be used when circular filter dependencies are not allowed.
 *
 * @public
 */
export const selectAttributeFilterDescendants = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilters, selectSupportsCircularDependencyInFilters, (attributeFilters, supportsCircularDependencies) => {
    invariant(!supportsCircularDependencies, "Filter descendants cannot be computed as this backend supports circular dependencies in filters. Do not use this selector.");
    const toCheck = compact([localId]);
    const result = new Set();
    while (toCheck.length) {
        const current = toCheck.pop();
        const children = attributeFilters.filter((f) => f.attributeFilter.filterElementsBy?.some((parent) => parent.filterLocalIdentifier === current));
        children.forEach((child) => {
            result.add(child.attributeFilter.localIdentifier);
            toCheck.push(child.attributeFilter.localIdentifier);
        });
    }
    return Array.from(result);
}));
/**
 * Creates a selector for selecting all element-based filters with different reference than the given one.
 *
 * @internal
 * @deprecated Use {@link selectOtherContextAttributeFilterItems} to include all attribute filter types.
 */
export const selectOtherContextAttributeFilters = createMemoizedSelector((ref) => createSelector(selectFilterContextAttributeFilters, (attributeFilters) => {
    return attributeFilters.filter((attributeFilter) => !areObjRefsEqual(attributeFilter.attributeFilter.displayForm, ref));
}));
/**
 * Creates a selector for selecting all attribute-based filters with different reference than the given one.
 * Includes element-based, arbitrary, and match attribute filters.
 *
 * @internal
 */
export const selectOtherContextAttributeFilterItems = createMemoizedSelector((ref) => createSelector(selectFilterContextAttributeFilterItems, (attributeFilters) => {
    return attributeFilters.filter((filter) => !areObjRefsEqual(dashboardAttributeFilterItemDisplayForm(filter), ref));
}));
/**
 * Creates a selector to get a display form of an element-based filter defined by its local identifier.
 *
 * @internal
 * @deprecated Use {@link selectAttributeFilterItemDisplayFormByLocalId} to include all attribute filter types.
 */
export const selectAttributeFilterDisplayFormByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilters, (filters) => {
    const filter = filters.find((filter) => filter.attributeFilter.localIdentifier === localId);
    invariant(filter, "Unable to find current filter to get its title.");
    return filter.attributeFilter.displayForm;
}));
/**
 * Creates a selector to get a display form of any attribute-based filter defined by its local identifier.
 * Matches element-based, arbitrary, and match attribute filters.
 *
 * @internal
 */
export const selectAttributeFilterItemDisplayFormByLocalId = createMemoizedSelector((localId) => createSelector(selectFilterContextAttributeFilterItems, (filters) => {
    const filter = filters.find((f) => dashboardAttributeFilterItemLocalIdentifier(f) === localId);
    invariant(filter, "Unable to find current filter to get its display form.");
    return dashboardAttributeFilterItemDisplayForm(filter);
}));
/**
 * Creates a selector which checks for a circular dependency between filters.
 *
 * @remarks
 * This selector should only be used when circular filter dependencies are not allowed.
 *
 * @internal
 */
export const selectIsCircularDependency = createMemoizedSelector((currentFilterLocalId, neighborFilterLocalId) => createSelector(selectAttributeFilterDescendants(currentFilterLocalId), selectSupportsCircularDependencyInFilters, (descendants, supportsCircularDependencies) => {
    invariant(!supportsCircularDependencies, "No need to compute circular dependencies as this backend supports it in filters. Do not use this selector.");
    return descendants.some((descendant) => descendant === neighborFilterLocalId);
}));
const MAX_DRAGGABLE_FILTERS_COUNT = 30;
/**
 * This selector returns whether any more attribute filters can be added.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 * @deprecated use selectCanAddMoreFilters instead
 */
export const selectCanAddMoreAttributeFilters = createSelector(selectFilterContextAttributeFilterItems, selectFilterContextMeasureValueFilters, selectFilterContextDateFiltersWithDimension, selectCrossFilteringFiltersLocalIdentifiers, (attributeFilterItems, measureValueFilters, dateFiltersWithDimension, crossFilters) => {
    return (attributeFilterItems.length +
        measureValueFilters.length +
        dateFiltersWithDimension.length -
        crossFilters.length <
        MAX_DRAGGABLE_FILTERS_COUNT);
});
/**
 * This selector returns whether any more attribute filters can be added.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 * Invocations before initialization lead to invariant errors.
 *
 * @public
 */
export const selectCanAddMoreFilters = selectCanAddMoreAttributeFilters;
/**
 * This selector returns information whether attribute filter from filter context has some dependencies.
 *
 * @internal
 */
export const selectIsAttributeFilterDependentByLocalIdentifier = createMemoizedSelector((attributeFilterLocalIdentifier) => createSelector(selectFilterContextAttributeFilterItemByLocalId(attributeFilterLocalIdentifier), (filterContextAttributeFilter) => {
    if (!filterContextAttributeFilter) {
        return false;
    }
    return (!isEmpty(dashboardAttributeFilterItemFilterElementsBy(filterContextAttributeFilter)) ||
        !isEmpty(dashboardAttributeFilterItemFilterElementsByDate(filterContextAttributeFilter)));
}));
/**
 * Returns filters for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Filters for the specified tab
 *
 * @internal
 */
export const selectFiltersForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFiltersByTab, (filtersByTab) => {
    return filtersByTab[tabLocalIdentifier] ?? [];
}));
/**
 * Returns working filter context definition for a specific tab.
 * This merges the applied filters with working filter definition for the tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Working filter context definition for the specified tab
 *
 * @internal
 */
export const selectWorkingFilterContextDefinitionForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectFilterContextStatesByTab, (statesByTab) => {
    const state = statesByTab[tabLocalIdentifier];
    if (!state?.filterContextDefinition) {
        return undefined;
    }
    return applyFilterContext(state.filterContextDefinition, state.workingFilterContextDefinition);
}));
/**
 * Returns working filters for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Working filters for the specified tab
 *
 * @internal
 */
export const selectWorkingFiltersForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectWorkingFilterContextDefinitionForTab(tabLocalIdentifier), (workingDefinition) => {
    return workingDefinition?.filters ?? [];
}));
/**
 * Returns working element-based attribute filters for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Working attribute filters for the specified tab
 *
 * @internal
 * @deprecated Use {@link selectWorkingFilterContextAttributeFilterItemsForTab} to include all attribute filter types.
 */
export const selectWorkingFilterContextAttributeFiltersForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectWorkingFiltersForTab(tabLocalIdentifier), (filters) => {
    return filters.filter(isDashboardAttributeFilter);
}));
/**
 * Returns all working attribute-based filters (element-based, arbitrary, and match) for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns All working attribute-based filters for the specified tab
 *
 * @internal
 */
export const selectWorkingFilterContextAttributeFilterItemsForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectWorkingFiltersForTab(tabLocalIdentifier), (filters) => {
    return filters.filter(isDashboardAttributeFilterItem);
}));
/**
 * Returns working date filters with dimension for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Working date filters with dimension for the specified tab
 *
 * @internal
 */
export const selectWorkingFilterContextDateFiltersWithDimensionForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectWorkingFiltersForTab(tabLocalIdentifier), (filters) => {
    return filters.filter(isDashboardDateFilterWithDimension);
}));
/**
 * Returns the working common date filter for a specific tab.
 *
 * @param tabLocalIdentifier - Tab local identifier
 * @returns Working common date filter for the specified tab, or undefined if not found
 *
 * @internal
 */
export const selectWorkingFilterContextDateFilterForTab = createMemoizedSelector((tabLocalIdentifier) => createSelector(selectWorkingFiltersForTab(tabLocalIdentifier), (filters) => {
    return filters.find(isDashboardCommonDateFilter);
}));
/**
 * Returns information whether attribute filter from a specific tab's filter context has some dependencies.
 *
 * @param attributeFilterLocalIdentifier - Filter local identifier
 * @param tabLocalIdentifier - Tab local identifier
 * @returns True if the filter has dependencies, false otherwise
 *
 * @internal
 */
export const selectIsAttributeFilterDependentByLocalIdentifierForTab = createMemoizedSelector((attributeFilterLocalIdentifier, tabLocalIdentifier) => createSelector(selectFilterContextAttributeFilterItemByLocalIdForTab(attributeFilterLocalIdentifier, tabLocalIdentifier), (filterContextAttributeFilter) => {
    if (!filterContextAttributeFilter) {
        return false;
    }
    return (!isEmpty(dashboardAttributeFilterItemFilterElementsBy(filterContextAttributeFilter)) ||
        !isEmpty(dashboardAttributeFilterItemFilterElementsByDate(filterContextAttributeFilter)));
}));
/**
 * Select preloaded attribute metadata objects with references for attribute filters.
 * Returns merged attributes from all filters across all tabs.
 *
 * @internal
 */
export const selectPreloadedAttributesWithReferences = createSelector(selectTabsState, (tabsState) => {
    return tabsState.attributesWithReferences;
});
/**
 * This selector returns an array of attribute names related to filters with invalid selection.
 *
 * @remarks
 * It is expected that the selector is called only after the filter context state is correctly initialized.
 *
 * @returns An array of attribute names that have filters with invalid selections.
 *
 * @public
 */
export const selectNamesOfFiltersWithInvalidSelection = createSelector(selectFiltersWithInvalidSelection, selectAttributeFilterDisplayForms, selectPreloadedAttributesWithReferences, selectFilterContextAttributeFilterItems, (invalidFilterIds, attributeFilterDisplayForms, attributesWithReferences, attributeFilterItems) => {
    // Find filters with invalid selection
    const invalidFilters = attributeFilterItems.filter((filter) => {
        const localId = dashboardAttributeFilterItemLocalIdentifier(filter);
        return localId && invalidFilterIds.includes(localId);
    });
    // For each invalid filter, resolve its name:
    // 1. Use the filter's own title (alias) if set
    // 2. Otherwise, look up the attribute title via displayForm -> attribute ref
    const attributeNames = invalidFilters.map((filter) => {
        // Filter alias takes priority
        const filterTitle = dashboardAttributeFilterItemTitle(filter);
        if (filterTitle) {
            return filterTitle;
        }
        // Look up the display form metadata to get the attribute ref
        const displayFormRef = dashboardAttributeFilterItemDisplayForm(filter);
        const displayForm = attributeFilterDisplayForms?.find((item) => areObjRefsEqual(item.ref, displayFormRef));
        // Use the attribute ref from the display form to find the attribute and its title
        if (displayForm?.attribute && attributesWithReferences) {
            const attrWithRefs = attributesWithReferences.find((item) => areObjRefsEqual(item.attribute.ref, displayForm.attribute));
            if (attrWithRefs) {
                return attrWithRefs.attribute.title;
            }
        }
        return "";
    });
    // Filter out empty strings and remove duplicates
    return [...new Set(attributeNames.filter(Boolean))];
});
/**
 * Selects default filter overrides for the dashboard.
 *
 * @beta
 */
export const selectDefaultFilterOverrides = createSelector(selectSelf, (state) => state.defaultFilterOverrides);
