// (C) 2021-2026 GoodData Corporation
import { compact, partition, uniqBy } from "lodash-es";
import { batchActions } from "redux-batched-actions";
import { all, call, put, select } from "redux-saga/effects";
import { NotSupported } from "@gooddata/sdk-backend-spi";
import { areObjRefsEqual, attributeElementsIsEmpty, dashboardAttributeFilterItemDisplayForm, dashboardAttributeFilterItemFilterElementsBy, dashboardAttributeFilterItemFilterElementsByDate, dashboardAttributeFilterItemLocalIdentifier, dashboardAttributeFilterItemValidateElementsBy, getAttributeElementsItems, isAllTimeDashboardDateFilter, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardAttributeFilterItem, isDashboardCommonDateFilter, isDashboardDateFilter, isDashboardMeasureValueFilter, isDashboardTextAttributeFilter, isSingleSelectionFilter, isUriRef, objRefToString, serializeObjRef, updateAttributeElementsItems, } from "@gooddata/sdk-model";
import { dashboardFilterToFilterContextItem } from "../../../_staging/dashboard/dashboardFilterContext.js";
import { invalidArgumentsProvided } from "../../events/general.js";
import { dispatchDashboardEvent } from "../../store/_infra/eventDispatcher.js";
import { selectAttributeFilterConfigsOverrides, selectAttributeFilterConfigsOverridesByTab, selectAttributeFilterConfigsSelectionTypeMap, selectAttributeFilterConfigsSelectionTypeMapByTab, } from "../../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
import { selectAttributeFilterDisplayFormsMap, selectFilterContextAttributeFilterByDisplayForm, selectFilterContextAttributeFilterByDisplayFormForTab, selectFilterContextAttributeFilterByLocalId, selectFilterContextAttributeFilterByLocalIdForTab, selectFilterContextAttributeFilterItemByDisplayForm, selectFilterContextAttributeFilterItemByDisplayFormForTab, selectFilterContextAttributeFilterItemByLocalId, selectFilterContextAttributeFilterItemByLocalIdForTab, selectFilterContextAttributeFilterItems, selectFilterContextAttributeFilterItemsForTab, selectFilterContextAttributeFilters, selectFilterContextAttributeFiltersForTab, selectFilterContextDateFilterByDataSet, selectFilterContextDateFilterByDataSetForTab, selectFilterContextDateFiltersWithDimension, selectFilterContextDateFiltersWithDimensionForTab, selectFilterContextMeasureValueFilters, } from "../../store/tabs/filterContext/filterContextSelectors.js";
import { tabsActions } from "../../store/tabs/index.js";
import { selectTabs } from "../../store/tabs/tabsSelectors.js";
import { resolveAttributeMetadata } from "../../utils/attributeResolver.js";
import { resolveDisplayFormMetadata, } from "../../utils/displayFormResolver.js";
import { canApplyFilterTypeToTarget } from "../dashboard/common/attributeFilterSelectionTypeCompatibility.js";
import { resolveAndRegisterDisplayFormMetadata } from "./attributeFilter/resolveDisplayFormMetadata.js";
import { canApplyDateFilter, dispatchFilterContextChanged } from "./common.js";
// Tab-aware select helpers — encapsulate the "if tab select ForTab else select" pattern.
// Named after the base selector they wrap.
function* selectFilterContextAttributeFilterByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFilterByDisplayFormForTab(displayForm, tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilterByDisplayForm(displayForm));
}
function* selectFilterContextAttributeFilterItemByLocalIdTabAware(localId, tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFilterItemByLocalIdForTab(localId, tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilterItemByLocalId(localId));
}
function* selectFilterContextAttributeFilterItemByDisplayFormTabAware(displayForm, tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFilterItemByDisplayFormForTab(displayForm, tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilterItemByDisplayForm(displayForm));
}
function* selectFilterContextAttributeFilterByLocalIdTabAware(localId, tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFilterByLocalIdForTab(localId, tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilterByLocalId(localId));
}
function* selectFilterContextAttributeFiltersTabAware(tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFiltersForTab(tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilters);
}
function* selectFilterContextAttributeFilterItemsTabAware(tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextAttributeFilterItemsForTab(tabLocalIdentifier))
        : yield select(selectFilterContextAttributeFilterItems);
}
function* selectFilterContextDateFilterByDataSetTabAware(dataSet, tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextDateFilterByDataSetForTab(dataSet, tabLocalIdentifier))
        : yield select(selectFilterContextDateFilterByDataSet(dataSet));
}
function* selectFilterContextDateFiltersWithDimensionTabAware(tabLocalIdentifier) {
    return tabLocalIdentifier
        ? yield select(selectFilterContextDateFiltersWithDimensionForTab(tabLocalIdentifier))
        : yield select(selectFilterContextDateFiltersWithDimension);
}
function* selectAttributeFilterConfigsSelectionTypeMapTabAware(tabLocalIdentifier) {
    if (tabLocalIdentifier) {
        const byTab = yield select(selectAttributeFilterConfigsSelectionTypeMapByTab);
        return byTab[tabLocalIdentifier] ?? new Map();
    }
    return yield select(selectAttributeFilterConfigsSelectionTypeMap);
}
export function* changeFilterContextSelectionHandler(ctx, cmd) {
    const { filters, resetOthers, attributeFilterConfigs = [], tabLocalIdentifier } = cmd.payload;
    // Validate that the target tab exists if tabLocalIdentifier is provided
    if (tabLocalIdentifier) {
        const tabs = yield select(selectTabs);
        const tabExists = tabs?.some((tab) => tab.localIdentifier === tabLocalIdentifier);
        if (!tabExists) {
            yield dispatchDashboardEvent(invalidArgumentsProvided(ctx, cmd, `Tab with local identifier "${tabLocalIdentifier}" does not exist.`));
            return;
        }
    }
    // Cross-filtering is always compatible with dashboard tabs now
    // (removed the check that prevented cross-filtering without tabs)
    const normalizedFilters = filters.map((filter) => {
        if (isDashboardAttributeFilterItem(filter) ||
            isDashboardDateFilter(filter) ||
            isDashboardMeasureValueFilter(filter)) {
            return filter;
        }
        else {
            return dashboardFilterToFilterContextItem(filter, !!ctx.backend.capabilities.supportsMultipleDateFilters);
        }
    });
    // Separate text filter types (arbitrary, match) — they use whole-filter replacement
    const textAttributeFiltersRaw = normalizedFilters.filter((filter) => isDashboardAttributeFilterItem(filter) && !isDashboardAttributeFilter(filter));
    // Deduplicate text filters by localIdentifier (last occurrence wins, matching batch behavior)
    const textAttributeFilters = uniqBy(textAttributeFiltersRaw
        .filter((f) => dashboardAttributeFilterItemLocalIdentifier(f))
        .slice()
        .reverse(), (f) => dashboardAttributeFilterItemLocalIdentifier(f)).reverse();
    const supportedFilters = normalizedFilters.filter((filter) => isDashboardAttributeFilter(filter) || isDashboardDateFilter(filter));
    // Measure value filters use whole-condition replacement keyed by localIdentifier; deduplicate
    // by localIdentifier (last occurrence wins, mirroring text attribute filter handling).
    const measureValueFilters = uniqBy(normalizedFilters.filter(isDashboardMeasureValueFilter).slice().reverse(), (f) => f.dashboardMeasureValueFilter.localIdentifier).reverse();
    const uniqueFilters = uniqBy(supportedFilters, (filter) => {
        const identification = isDashboardAttributeFilter(filter)
            ? dashboardAttributeFilterItemDisplayForm(filter)
            : filter.dateFilter.dataSet;
        let config;
        if (isDashboardAttributeFilter(filter)) {
            config = attributeFilterConfigs.find((config) => config.localIdentifier === dashboardAttributeFilterItemLocalIdentifier(filter));
        }
        // do not remove duplicates using same primary label, but different display as label
        const secondaryIdentification = config?.displayAsLabel ? objRefToString(config.displayAsLabel) : "";
        return identification
            ? objRefToString(identification) + secondaryIdentification
            : identification + secondaryIdentification;
    });
    const [dateFilters, attributeFilters] = partition(uniqueFilters, isDashboardDateFilter);
    const [[commonDateFilter], dateFiltersWithDimension] = partition(dateFilters, isDashboardCommonDateFilter);
    const selectionTypeMap = yield call(selectAttributeFilterConfigsSelectionTypeMapTabAware, tabLocalIdentifier);
    const [attributeFilterResult, commonDateFilterUpdateActions, dateFiltersUpdateActions, measureValueFilterUpdateActions,] = yield all([
        call(getAttributeFiltersUpdateActions, [...attributeFilters].reverse(), textAttributeFilters, attributeFilterConfigs, resetOthers, ctx, tabLocalIdentifier, selectionTypeMap),
        call(getDateFilterUpdateActions, commonDateFilter, resetOthers, tabLocalIdentifier),
        call(getDateFiltersUpdateActions, dateFiltersWithDimension, resetOthers, tabLocalIdentifier),
        call(getMeasureValueFiltersUpdateActions, measureValueFilters, resetOthers),
    ]);
    // Ensure display form metadata is loaded for filters that will actually be applied.
    // This handles the case where a default saved view changed filter types (e.g., list→text)
    // and then local storage restores original types whose display forms weren't loaded during init.
    if (attributeFilterResult.displayFormsToResolve.length > 0) {
        const displayFormsMap = yield select(selectAttributeFilterDisplayFormsMap);
        for (const displayForm of attributeFilterResult.displayFormsToResolve) {
            if (!displayFormsMap.get(displayForm)) {
                yield call(resolveAndRegisterDisplayFormMetadata, displayForm);
            }
        }
    }
    yield put(batchActions([
        ...attributeFilterResult.actions,
        ...commonDateFilterUpdateActions,
        ...dateFiltersUpdateActions,
        ...measureValueFilterUpdateActions,
    ]));
    yield call(dispatchFilterContextChanged, ctx, cmd, tabLocalIdentifier);
}
function* getDashboardFilterByAttributeMatching(filterRef, resolvedDisplayForms, ctx, tabLocalIdentifier) {
    if (isUriRef(filterRef) && !ctx.backend.capabilities.supportsObjectUris) {
        throw new NotSupported("Unsupported filter ObjRef! Please provide IdentifierRef instead of UriRef.");
    }
    const filterDF = resolvedDisplayForms.resolved.get(filterRef);
    const resolvedAttribute = yield call(resolveAttributeMetadata, ctx, compact([filterDF?.attribute]));
    const attribute = filterDF?.attribute && resolvedAttribute.resolved.get(filterDF?.attribute);
    for (const displayForm of attribute?.displayForms ?? []) {
        const dashboardFilter = yield call(selectFilterContextAttributeFilterByDisplayFormTabAware, displayForm.ref, tabLocalIdentifier);
        if (dashboardFilter) {
            return dashboardFilter;
        }
    }
    return null;
}
function* getDashboardFilterByDisplayAsLabelMatching(attributeFilter, attributeFilterConfigs, tabLocalIdentifier) {
    let foundByDisplayAsLabel = false;
    let foundByDashboardFilterDisplayAsLabel = false;
    let dashboardFilter = undefined;
    const filterRef = dashboardAttributeFilterItemDisplayForm(attributeFilter);
    const filterConfig = attributeFilterConfigs.find((config) => config.localIdentifier === dashboardAttributeFilterItemLocalIdentifier(attributeFilter));
    if (filterConfig?.displayAsLabel) {
        dashboardFilter = (yield call(selectFilterContextAttributeFilterByDisplayFormTabAware, filterConfig.displayAsLabel, tabLocalIdentifier));
        foundByDisplayAsLabel = !!dashboardFilter;
    }
    if (!foundByDisplayAsLabel) {
        const dashboardFiltersConfigsForActiveTab = yield select(selectAttributeFilterConfigsOverrides);
        const dashboardFiltersConfigsByTab = yield select(selectAttributeFilterConfigsOverridesByTab);
        const dashboardFiltersConfigs = tabLocalIdentifier
            ? dashboardFiltersConfigsByTab[tabLocalIdentifier]
            : dashboardFiltersConfigsForActiveTab;
        const matchingDashboardFilterConfig = dashboardFiltersConfigs.find((config) => areObjRefsEqual(config.displayAsLabel, filterRef));
        if (matchingDashboardFilterConfig) {
            dashboardFilter = (yield call(selectFilterContextAttributeFilterByLocalIdTabAware, matchingDashboardFilterConfig?.localIdentifier, tabLocalIdentifier));
            foundByDashboardFilterDisplayAsLabel = !!dashboardFilter;
        }
    }
    return { foundByDisplayAsLabel, foundByDashboardFilterDisplayAsLabel, dashboardFilter };
}
/**
 * Finds a matching filter item on the target dashboard for a text filter using displayForm-based matching.
 * Tries:
 * 1. Direct displayForm match (works for text→text and text→list by primary displayForm)
 * 2. DisplayAsLabel match (text filter's displayForm matches a list filter's displayAsLabel in config)
 */
function* findTargetFilterItemByDisplayForm(displayFormRef, tabLocalIdentifier) {
    // Direct displayForm match — covers text→text and text→list (by primary displayForm)
    const directMatch = yield call(selectFilterContextAttributeFilterItemByDisplayFormTabAware, displayFormRef, tabLocalIdentifier);
    if (directMatch) {
        return directMatch;
    }
    // DisplayAsLabel match — text filter's displayForm may match a list filter's displayAsLabel
    const dashboardFiltersConfigsForActiveTab = yield select(selectAttributeFilterConfigsOverrides);
    const dashboardFiltersConfigsByTab = yield select(selectAttributeFilterConfigsOverridesByTab);
    const dashboardFiltersConfigs = tabLocalIdentifier
        ? dashboardFiltersConfigsByTab[tabLocalIdentifier]
        : dashboardFiltersConfigsForActiveTab;
    const matchingConfig = dashboardFiltersConfigs?.find((config) => areObjRefsEqual(config.displayAsLabel, displayFormRef));
    if (matchingConfig) {
        return yield call(selectFilterContextAttributeFilterItemByLocalIdTabAware, matchingConfig.localIdentifier, tabLocalIdentifier);
    }
    return undefined;
}
function* getAttributeFiltersUpdateActions(attributeFilters, textAttributeFilters, attributeFilterConfigs, resetOthers, ctx, tabLocalIdentifier, selectionTypeMap) {
    const updateActions = [];
    const displayFormsToResolve = [];
    const handledLocalIds = new Set();
    const resolvedDisplayForms = yield call(resolveDisplayFormMetadata, ctx, attributeFilters.map((af) => dashboardAttributeFilterItemDisplayForm(af)));
    for (const attributeFilter of attributeFilters) {
        const filterRef = dashboardAttributeFilterItemDisplayForm(attributeFilter);
        // only attribute filters with elements are relevant
        let dashboardFilter = yield call(selectFilterContextAttributeFilterByDisplayFormTabAware, filterRef, tabLocalIdentifier);
        if (!dashboardFilter && canMapDashboardFilterFromAnotherDisplayForm(ctx)) {
            dashboardFilter = (yield call(getDashboardFilterByAttributeMatching, filterRef, resolvedDisplayForms, ctx, tabLocalIdentifier));
        }
        let foundByDisplayAsLabel = false;
        let foundByDashboardFilterDisplayAsLabel = false;
        if (!dashboardFilter) {
            const result = yield call(getDashboardFilterByDisplayAsLabelMatching, attributeFilter, attributeFilterConfigs, tabLocalIdentifier);
            foundByDisplayAsLabel = result.foundByDisplayAsLabel;
            foundByDashboardFilterDisplayAsLabel = result.foundByDashboardFilterDisplayAsLabel;
            dashboardFilter = result.dashboardFilter;
        }
        const displayFormData = resolvedDisplayForms.resolved.get(filterRef);
        if (dashboardFilter) {
            const dashboardFilterLocalId = dashboardAttributeFilterItemLocalIdentifier(dashboardFilter);
            const dashboardFilterDisplayForm = dashboardAttributeFilterItemDisplayForm(dashboardFilter);
            if (foundByDisplayAsLabel && displayFormData) {
                updateActions.push(
                // keep the attribute display form field up to date
                tabsActions.addAttributeFilterDisplayForm(tabLocalIdentifier
                    ? { displayForm: displayFormData, tabLocalIdentifier }
                    : displayFormData), tabsActions.changeAttributeDisplayForm({
                    filterLocalId: dashboardFilterLocalId,
                    displayForm: filterRef,
                    tabLocalIdentifier,
                }), 
                // backup current displayForm to the displayAsLabel
                tabsActions.changeDisplayAsLabel({
                    localIdentifier: dashboardFilterLocalId,
                    displayAsLabel: dashboardFilterDisplayForm,
                    tabLocalIdentifier,
                }));
            }
            if (foundByDashboardFilterDisplayAsLabel && displayFormData) {
                updateActions.push(
                // keep the attribute display form field up to date
                tabsActions.addAttributeFilterDisplayForm(tabLocalIdentifier
                    ? { displayForm: displayFormData, tabLocalIdentifier }
                    : displayFormData), tabsActions.changeAttributeDisplayForm({
                    filterLocalId: dashboardFilterLocalId,
                    displayForm: filterRef,
                    tabLocalIdentifier,
                }), 
                // clear displayAsLabel
                tabsActions.changeDisplayAsLabel({
                    localIdentifier: dashboardFilterLocalId,
                    displayAsLabel: undefined,
                    tabLocalIdentifier,
                }));
            }
            displayFormsToResolve.push(filterRef);
            updateActions.push(tabsActions.updateAttributeFilterSelection({
                ...getAttributeFilterSelectionPayload(attributeFilter, dashboardFilter),
                tabLocalIdentifier,
            }));
            handledLocalIds.add(dashboardFilterLocalId);
        }
        else {
            // No list filter found by displayForm — check if a text filter exists
            // that can accept the incoming list filter (list→text migration).
            // Try localId first (same-dashboard), then displayForm fallback (cross-dashboard drill).
            const sourceLocalId = dashboardAttributeFilterItemLocalIdentifier(attributeFilter);
            const sourceDisplayForm = dashboardAttributeFilterItemDisplayForm(attributeFilter);
            let targetTextFilter;
            if (sourceLocalId) {
                targetTextFilter = (yield call(selectFilterContextAttributeFilterItemByLocalIdTabAware, sourceLocalId, tabLocalIdentifier));
            }
            // DisplayForm fallback for cross-dashboard drill where localIds differ
            if (!targetTextFilter) {
                targetTextFilter = (yield call(findTargetFilterItemByDisplayForm, sourceDisplayForm, tabLocalIdentifier));
                // Also try source's displayAsLabel from incoming attributeFilterConfigs
                if (!targetTextFilter) {
                    const incomingConfig = attributeFilterConfigs.find((config) => config.localIdentifier === sourceLocalId);
                    if (incomingConfig?.displayAsLabel) {
                        targetTextFilter = (yield call(findTargetFilterItemByDisplayForm, incomingConfig.displayAsLabel, tabLocalIdentifier));
                    }
                }
            }
            if (targetTextFilter && isDashboardTextAttributeFilter(targetTextFilter)) {
                const targetLocalId = dashboardAttributeFilterItemLocalIdentifier(targetTextFilter);
                const configSelectionType = selectionTypeMap?.get(targetLocalId);
                if (canApplyFilterTypeToTarget("list", configSelectionType, targetTextFilter)) {
                    // Text → list: set displayAsLabel to the text filter's displayForm
                    // so the list filter visually shows the same label.
                    // Normalize: use target's localIdentifier so attributeFilterConfigs
                    // (keyed by localId) keep matching, and strip selectionMode so the
                    // target's own config determines single/multi presentation.
                    const normalizedListFilter = {
                        attributeFilter: {
                            ...attributeFilter.attributeFilter,
                            localIdentifier: targetLocalId,
                            selectionMode: undefined,
                        },
                    };
                    updateActions.push(tabsActions.replaceAttributeFilterItem({
                        filterLocalId: targetLocalId,
                        filter: normalizedListFilter,
                        tabLocalIdentifier,
                    }), tabsActions.changeDisplayAsLabel({
                        localIdentifier: targetLocalId,
                        displayAsLabel: dashboardAttributeFilterItemDisplayForm(targetTextFilter),
                        tabLocalIdentifier,
                    }));
                    displayFormsToResolve.push(sourceDisplayForm);
                    handledLocalIds.add(targetLocalId);
                }
            }
        }
    }
    // Build replace actions for text filter types (arbitrary, match).
    for (const textFilter of textAttributeFilters) {
        const localId = dashboardAttributeFilterItemLocalIdentifier(textFilter);
        if (!localId) {
            continue;
        }
        // 1. Try localId match first (same-dashboard scenario)
        let existingFilter = yield call(selectFilterContextAttributeFilterItemByLocalIdTabAware, localId, tabLocalIdentifier);
        // 2. DisplayForm fallback for cross-dashboard drill where localIds differ
        if (!existingFilter) {
            const textDisplayForm = dashboardAttributeFilterItemDisplayForm(textFilter);
            existingFilter = (yield call(findTargetFilterItemByDisplayForm, textDisplayForm, tabLocalIdentifier));
        }
        if (!existingFilter) {
            continue;
        }
        const targetLocalId = dashboardAttributeFilterItemLocalIdentifier(existingFilter);
        if (handledLocalIds.has(targetLocalId)) {
            continue;
        }
        // Check if migration is allowed based on selection type config
        const isExistingText = isDashboardTextAttributeFilter(existingFilter);
        if (!isExistingText) {
            // Text → list migration: check if target's selection type allows text
            const configSelectionType = selectionTypeMap?.get(targetLocalId);
            if (!canApplyFilterTypeToTarget("text", configSelectionType, existingFilter)) {
                continue;
            }
        }
        // Normalize: use target's localIdentifier so attributeFilterConfigs
        // (keyed by localId) keep matching after the replacement.
        const normalizedTextFilter = normalizeFilterLocalIdentifier(textFilter, targetLocalId);
        updateActions.push(tabsActions.replaceAttributeFilterItem({
            filterLocalId: targetLocalId,
            filter: normalizedTextFilter,
            tabLocalIdentifier,
        }));
        const textDisplayForm = dashboardAttributeFilterItemDisplayForm(textFilter);
        // List → text: update displayAsLabel to match the text filter's displayForm
        if (!isExistingText) {
            updateActions.push(tabsActions.changeDisplayAsLabel({
                localIdentifier: targetLocalId,
                displayAsLabel: textDisplayForm,
                tabLocalIdentifier,
            }));
        }
        // Ensure display-form metadata is available for both list → text
        // and text → text replacement.
        displayFormsToResolve.push(textDisplayForm);
        handledLocalIds.add(targetLocalId);
    }
    if (resetOthers) {
        const currentAttributeFilters = yield call(selectFilterContextAttributeFiltersTabAware, tabLocalIdentifier);
        // for element-based filters that have not been handled, create clear selection actions
        const unhandledFilters = currentAttributeFilters.filter((filter) => !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)));
        if (unhandledFilters.length > 0) {
            updateActions.push(tabsActions.clearAttributeFiltersSelection({
                filterLocalIds: unhandledFilters.map((filter) => dashboardAttributeFilterItemLocalIdentifier(filter)),
                tabLocalIdentifier,
            }));
        }
        // for text filters (arbitrary/match) that have not been handled, reset to All state
        // (negative arbitrary filter with empty selection)
        const currentAllFilterItems = yield call(selectFilterContextAttributeFilterItemsTabAware, tabLocalIdentifier);
        const unhandledTextFilters = currentAllFilterItems.filter((filter) => isDashboardTextAttributeFilter(filter) &&
            !handledLocalIds.has(dashboardAttributeFilterItemLocalIdentifier(filter)));
        for (const textFilter of unhandledTextFilters) {
            const localId = dashboardAttributeFilterItemLocalIdentifier(textFilter);
            const resetFilter = {
                arbitraryAttributeFilter: {
                    displayForm: dashboardAttributeFilterItemDisplayForm(textFilter),
                    values: [],
                    negativeSelection: true,
                    localIdentifier: localId,
                    filterElementsBy: dashboardAttributeFilterItemFilterElementsBy(textFilter),
                    filterElementsByDate: dashboardAttributeFilterItemFilterElementsByDate(textFilter),
                    validateElementsBy: dashboardAttributeFilterItemValidateElementsBy(textFilter),
                },
            };
            updateActions.push(tabsActions.replaceAttributeFilterItem({
                filterLocalId: localId,
                filter: resetFilter,
                tabLocalIdentifier,
            }));
        }
    }
    return { actions: updateActions, displayFormsToResolve };
}
function* getDateFilterUpdateActions(dateFilter, resetOthers, tabLocalIdentifier) {
    if (dateFilter) {
        const canApply = yield call(canApplyDateFilter, dateFilter);
        if (!canApply) {
            return [];
        }
        const localIdentifierObj = dateFilter.dateFilter.localIdentifier
            ? { localIdentifier: dateFilter.dateFilter.localIdentifier }
            : {};
        const emptyValueHandlingObj = dateFilter.dateFilter.emptyValueHandling
            ? { emptyValueHandling: dateFilter.dateFilter.emptyValueHandling }
            : {};
        const upsertPayload = isAllTimeDashboardDateFilter(dateFilter)
            ? {
                type: "allTime",
                dataSet: dateFilter.dateFilter.dataSet,
                ...localIdentifierObj,
                ...emptyValueHandlingObj,
                tabLocalIdentifier,
            }
            : {
                type: dateFilter.dateFilter.type,
                granularity: dateFilter.dateFilter.granularity,
                from: dateFilter.dateFilter.from,
                to: dateFilter.dateFilter.to,
                dataSet: dateFilter.dateFilter.dataSet,
                ...localIdentifierObj,
                ...(dateFilter.dateFilter.boundedFilter
                    ? { boundedFilter: dateFilter.dateFilter.boundedFilter }
                    : {}),
                ...emptyValueHandlingObj,
                tabLocalIdentifier,
            };
        return [tabsActions.upsertDateFilter(upsertPayload)];
    }
    else if (resetOthers) {
        return [tabsActions.upsertDateFilter({ type: "allTime", tabLocalIdentifier })];
    }
    return [];
}
function* getMeasureValueFiltersUpdateActions(measureValueFilters, resetOthers) {
    const updateActions = [];
    const handledLocalIds = new Set();
    const currentMeasureValueFilters = yield select(selectFilterContextMeasureValueFilters);
    const currentByLocalId = new Map(currentMeasureValueFilters.map((f) => [f.dashboardMeasureValueFilter.localIdentifier, f]));
    for (const incoming of measureValueFilters) {
        const localIdentifier = incoming.dashboardMeasureValueFilter.localIdentifier;
        // Only update filters that already exist on the dashboard. Adding/removing MVFs is an
        // edit-mode operation and is not part of changeFilterContextSelection in view mode.
        if (!currentByLocalId.has(localIdentifier)) {
            console.warn(`changeFilterContextSelection: ignoring measure value filter with unknown localIdentifier "${localIdentifier}". The dashboard does not currently have a measure value filter with this id; adding new MVFs is not supported in view mode.`);
            continue;
        }
        handledLocalIds.add(localIdentifier);
        updateActions.push(tabsActions.changeMeasureValueFilterCondition({
            localIdentifier,
            conditions: incoming.dashboardMeasureValueFilter.conditions,
        }));
    }
    if (resetOthers) {
        // Reset any current MVF that wasn't covered by the incoming payload back to the "All"
        // state (no conditions). Mirrors the behavior of attribute and date filters.
        for (const current of currentMeasureValueFilters) {
            const localIdentifier = current.dashboardMeasureValueFilter.localIdentifier;
            if (handledLocalIds.has(localIdentifier)) {
                continue;
            }
            updateActions.push(tabsActions.changeMeasureValueFilterCondition({
                localIdentifier,
                conditions: undefined,
            }));
        }
    }
    return updateActions;
}
function* getDateFiltersUpdateActions(dateFilters, resetOthers, tabLocalIdentifier) {
    const updateActions = [];
    const handledDataSets = new Set();
    for (const dateFilter of dateFilters) {
        const filterRef = dateFilter.dateFilter.dataSet;
        const dashboardFilter = yield call(selectFilterContextDateFilterByDataSetTabAware, filterRef, tabLocalIdentifier);
        if (dashboardFilter) {
            const localIdentifierObj = dateFilter.dateFilter.localIdentifier
                ? { localIdentifier: dateFilter.dateFilter.localIdentifier }
                : {};
            const emptyValueHandlingObj = dateFilter.dateFilter.emptyValueHandling
                ? { emptyValueHandling: dateFilter.dateFilter.emptyValueHandling }
                : {};
            handledDataSets.add(serializeObjRef(dashboardFilter.dateFilter.dataSet));
            const upsertPayload = isAllTimeDashboardDateFilter(dateFilter)
                ? {
                    type: "allTime",
                    dataSet: dateFilter.dateFilter.dataSet,
                    ...localIdentifierObj,
                    ...emptyValueHandlingObj,
                    tabLocalIdentifier,
                }
                : {
                    type: dateFilter.dateFilter.type,
                    granularity: dateFilter.dateFilter.granularity,
                    from: dateFilter.dateFilter.from,
                    to: dateFilter.dateFilter.to,
                    dataSet: dateFilter.dateFilter.dataSet,
                    ...localIdentifierObj,
                    ...(dateFilter.dateFilter.boundedFilter
                        ? { boundedFilter: dateFilter.dateFilter.boundedFilter }
                        : {}),
                    ...emptyValueHandlingObj,
                    tabLocalIdentifier,
                };
            updateActions.push(tabsActions.upsertDateFilter(upsertPayload));
        }
    }
    if (resetOthers) {
        const currentDateFilters = yield call(selectFilterContextDateFiltersWithDimensionTabAware, tabLocalIdentifier);
        // for filters that have not been handled by the loop above, create a clear selection actions
        const unhandledFilters = currentDateFilters.filter((filter) => !handledDataSets.has(serializeObjRef(filter.dateFilter.dataSet)));
        if (unhandledFilters.length > 0) {
            for (const dateFilter of unhandledFilters) {
                const localIdentifierObj = dateFilter.dateFilter.localIdentifier
                    ? { localIdentifier: dateFilter.dateFilter.localIdentifier }
                    : {};
                updateActions.push(tabsActions.upsertDateFilter({
                    type: "allTime",
                    dataSet: dateFilter.dateFilter.dataSet,
                    ...localIdentifierObj,
                    tabLocalIdentifier,
                }));
            }
        }
    }
    return updateActions;
}
const getAttributeFilterSelectionPayload = (incomingFilter, currentFilter) => {
    const { attributeElements, negativeSelection } = incomingFilter.attributeFilter;
    const attributeFilterSelectionPayload = {
        elements: attributeElements,
        filterLocalId: currentFilter.attributeFilter.localIdentifier,
        negativeSelection: negativeSelection,
    };
    if (isSingleSelectionFilter(currentFilter)) {
        if (negativeSelection) {
            return {
                ...attributeFilterSelectionPayload,
                elements: updateAttributeElementsItems(attributeElements, []),
                negativeSelection: false,
            };
        }
        if (!attributeElementsIsEmpty(attributeElements)) {
            const attributeElementsValues = getAttributeElementsItems(attributeElements);
            const singleSelectAttributeElements = [attributeElementsValues[0]];
            return {
                ...attributeFilterSelectionPayload,
                elements: updateAttributeElementsItems(attributeElements, singleSelectAttributeElements),
            };
        }
    }
    return attributeFilterSelectionPayload;
};
/**
 *  For Bear:
 *  Attribute element in Bear can be matched because Bear utilizes a unique URI to identify each attribute element.
 *  This URI remains constant regardless of the attribute label (the URI points to the attribute URI rather than the label URI).
 *  In Bear, there are duplicate values, and each label contains the same number of element values.
 *  As a result, we can easily map one value from one label to another, and enabling us to determine the customer's intended selection
 *
 *  For Tiger/Panther:
 *  Attribute elements in Tiger/Panther are referenced solely by the values that the user sees. There is no URI like in Bear.
 *  Only the display value is available in Tiger/Panther. Additionally, Tiger only displays unique values for each label.
 *  In Tiger/Panther, you may observe a varying number of element values in the labels if duplicates are present.
 *  Therefore, mapping one value from one label to another is not straightforward, as it can be mapped to multiple values,
 *  and it is not always possible to ascertain the customer's intended selection.
 */
function canMapDashboardFilterFromAnotherDisplayForm(ctx) {
    return ctx.backend.capabilities.supportsElementUris;
}
/**
 * Returns a shallow copy of the filter with its localIdentifier replaced.
 * Used when replacing a target filter with an incoming filter from another dashboard
 * to preserve the target's localIdentifier so that attributeFilterConfigs (keyed by localId)
 * continue to match.
 */
function normalizeFilterLocalIdentifier(filter, localIdentifier) {
    if (isDashboardAttributeFilter(filter)) {
        return {
            attributeFilter: { ...filter.attributeFilter, localIdentifier },
        };
    }
    if (isDashboardArbitraryAttributeFilter(filter)) {
        return {
            arbitraryAttributeFilter: { ...filter.arbitraryAttributeFilter, localIdentifier },
        };
    }
    // IDashboardMatchAttributeFilter
    return {
        matchAttributeFilter: {
            ...filter.matchAttributeFilter,
            localIdentifier,
        },
    };
}
//# sourceMappingURL=changeFilterContextSelectionHandler.js.map