// (C) 2021-2026 GoodData Corporation
import { all, call, select } from "redux-saga/effects";
import { generateDateFilterLocalIdentifier } from "@gooddata/sdk-backend-base";
import { areObjRefsEqual, filterLocalIdentifier, filterObjRef, insightFilters, isAttributeFilter, isDashboardAttributeFilterReference, isDashboardDateFilterReference, isDashboardMeasureValueFilterReference, isDateFilter, isInsightWidget, isMeasureValueFilter, isNoopAllTimeDateFilter, isRankingFilter, newAllTimeFilter, objRefToString, } from "@gooddata/sdk-model";
import { invalidQueryArguments } from "../events/general.js";
import { createQueryService } from "../store/_infra/queryService.js";
import { selectSupportsMultipleDateFilters } from "../store/backendCapabilities/backendCapabilitiesSelectors.js";
import { selectEnableDateFilterIdentifiers } from "../store/config/configSelectors.js";
import { selectInsightByRef } from "../store/insights/insightsSelectors.js";
import { selectAttributeFilterConfigsDisplayAsLabelMap } from "../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
import { selectAllFiltersForWidgetByRef, selectFilterableWidgetByRef, } from "../store/tabs/layout/layoutSelectors.js";
export const QueryWidgetFiltersService = createQueryService("GDC.DASH/QUERY.WIDGET.FILTERS", queryService);
function selectDateDatasetsForDateFilters(filters) {
    return filters.map((filter) => {
        return {
            dateDatasetLink: filterObjRef(filter),
            filter,
        };
    });
}
function* getResolvedInsightAttributeFilters(widget, dashboardAttributeFilters, insightAttributeFilters) {
    // only dashboard filters are subject to widget ignores
    const resolvedDashboardFilters = yield call(getResolvedAttributeFilters, widget, dashboardAttributeFilters);
    return [...resolvedDashboardFilters, ...insightAttributeFilters];
}
function* getResolvedAttributeFilters(widget, attributeFilters) {
    const displayAsLabelMap = yield select(selectAttributeFilterConfigsDisplayAsLabelMap);
    return resolveWidgetFilterIgnore(widget, attributeFilters, displayAsLabelMap);
}
function resolveWidgetFilterIgnore(widget, dashboardNonDateFilters, displayAsLabelMap) {
    return dashboardNonDateFilters.filter((filter) => {
        const filterDisplayForm = filterObjRef(filter);
        const matches = filterDisplayForm &&
            widget.ignoreDashboardFilters?.filter(isDashboardAttributeFilterReference).some((ignored) => {
                const filterLocalId = filterLocalIdentifier(filter);
                const displayAsLabel = filterLocalId ? displayAsLabelMap.get(filterLocalId) : undefined;
                // The filter definition already carries the refs needed to decide the ignore:
                // its primary display form (filterObjRef) and its displayAsLabel. Comparing these
                // local refs directly avoids fetching the display form metadata object per widget
                // (getAttributeDisplayForms), which previously fired once per widget on every load.
                return (areObjRefsEqual(ignored.displayForm, filterDisplayForm) ||
                    areObjRefsEqual(ignored.displayForm, displayAsLabel));
            });
        return !matches;
    });
}
function selectResolvedInsightDateFilters(widget, dashboardCommonDateFilters, dashboardDateFiltersWithDimensions, insightDateFilters, supportsMultipleDateFilters) {
    const nonIgnoredDashboardDateFilterDateDatasetPairs = selectResolveWidgetDateFilterIgnore(widget, dashboardCommonDateFilters, dashboardDateFiltersWithDimensions);
    const insightDateFilterDateDatasetPairs = selectDateDatasetsForDateFilters(insightDateFilters);
    return resolveDateFilters(insightDateFilterDateDatasetPairs, nonIgnoredDashboardDateFilterDateDatasetPairs, supportsMultipleDateFilters);
}
function selectResolveWidgetDateFilterIgnore(widget, dashboardCommonDateFilters, dashboardDateFiltersWithDimensions) {
    const commonDateFilterDateDatasetPairs = selectDateDatasetsForDateFilters(dashboardCommonDateFilters);
    const widgetDateFilterDateDatasetPairs = selectDateDatasetsForDateFilters(dashboardDateFiltersWithDimensions);
    return resolveWidgetDateFilterIgnore(widget, commonDateFilterDateDatasetPairs, widgetDateFilterDateDatasetPairs);
}
function resolveWidgetDateFilterIgnore(widget, commonDateFilterDateDatasetPairs, widgetDateFilterDateDatasetPairs) {
    const nonIgnoredCommonDateFilterDateDatasetPairs = commonDateFilterDateDatasetPairs.filter(({ dateDatasetLink }) => {
        return (!!widget.dateDataSet &&
            dateDatasetLink &&
            areObjRefsEqual(widget.dateDataSet, dateDatasetLink));
    });
    const nonIgnoredWidgetDateFilterDateDatasetPairs = widgetDateFilterDateDatasetPairs.filter(({ dateDatasetLink }) => {
        const matches = widget.ignoreDashboardFilters
            ?.filter(isDashboardDateFilterReference)
            .some((ignored) => dateDatasetLink && areObjRefsEqual(ignored.dataSet, dateDatasetLink));
        return !matches;
    });
    return [...nonIgnoredCommonDateFilterDateDatasetPairs, ...nonIgnoredWidgetDateFilterDateDatasetPairs];
}
function selectResolvedDateFilters(widget, dashboardCommonDateFilters, dashboardDateFiltersWithDimensions, supportsMultipleDateFilters) {
    const allDateFilterDateDatasetPairs = selectResolveWidgetDateFilterIgnore(widget, dashboardCommonDateFilters, dashboardDateFiltersWithDimensions);
    return resolveDateFilters([], allDateFilterDateDatasetPairs, supportsMultipleDateFilters);
}
function resolveDashboardMeasureValueFilters(widget, dashboardMeasureValueFilters) {
    const ignored = widget.ignoreDashboardFilters?.filter(isDashboardMeasureValueFilterReference) ?? [];
    if (ignored.length === 0) {
        return dashboardMeasureValueFilters;
    }
    return dashboardMeasureValueFilters.filter((filter) => {
        const measureRef = filter.measureValueFilter.measure;
        return !ignored.some((ref) => areObjRefsEqual(ref.measure, measureRef));
    });
}
function resolveDateFilters(insightDateFilterDateDatasetPairs, dashboardDateFilterDateDatasetPairs, supportsMultipleDateFilters) {
    // prioritize dashboard filters over insight ones
    // and strip useless all time filters at the end
    const init = dashboardDateFilterDateDatasetPairs
        .filter((item) => !!item.dateDatasetLink)
        .map((item) => item.filter);
    return insightDateFilterDateDatasetPairs
        .filter((item) => !!item.dateDatasetLink)
        .reduceRight((acc, curr) => {
        const alreadyPresent = acc.some((item) => areObjRefsEqual(filterObjRef(item), curr.dateDatasetLink));
        if (!alreadyPresent) {
            acc.push(curr.filter);
        }
        return acc;
    }, init)
        .filter((item) => {
        if (supportsMultipleDateFilters) {
            return true;
        }
        else {
            return !isNoopAllTimeDateFilter(item);
        }
    });
}
export function* queryWithInsight(_ctx, widget, insight) {
    const widgetAwareDashboardFiltersSelector = selectAllFiltersForWidgetByRef(widget.ref);
    const [widgetAwareDashboardCommonDateFilters, widgetAwareDashboardOtherFilters] = yield select(widgetAwareDashboardFiltersSelector);
    const supportsMultipleDateFilters = yield select(selectSupportsMultipleDateFilters);
    const enableDateFilterIdentifiers = yield select(selectEnableDateFilterIdentifiers);
    // add all time filter explicitly in case the date widgetAwareDashboardFilters are empty
    // this will cause the all time filter to be used instead of the insight date filter
    // if the dashboard date filter is not ignored by the widget
    if (!widgetAwareDashboardCommonDateFilters.length && widget.dateDataSet) {
        widgetAwareDashboardCommonDateFilters.push(newAllTimeFilter(widget.dateDataSet, enableDateFilterIdentifiers
            ? generateDateFilterLocalIdentifier(0, widget.dateDataSet)
            : undefined));
    }
    const effectiveInsightFilters = insightFilters(insight);
    const [dateFilters, attributeFilters] = yield all([
        call(selectResolvedInsightDateFilters, widget, widgetAwareDashboardCommonDateFilters.filter(isDateFilter), widgetAwareDashboardOtherFilters.filter(isDateFilter), effectiveInsightFilters.filter(isDateFilter), supportsMultipleDateFilters),
        call(getResolvedInsightAttributeFilters, widget, widgetAwareDashboardOtherFilters.filter(isAttributeFilter), effectiveInsightFilters.filter(isAttributeFilter)),
    ]);
    const insightRankingFilters = effectiveInsightFilters.filter(isRankingFilter);
    const insightHasRankingFilter = insightRankingFilters.length > 0;
    // The backend rejects executions that combine MVFs with ranking filters with
    // "Measure value filters with ranking filters unsupported.". AD prevents this in
    // insights at authoring time, so when the insight has a ranking filter we drop
    // all dashboard-level MVFs from this widget's execution to avoid the 400.
    const dashboardMeasureValueFilters = insightHasRankingFilter
        ? []
        : resolveDashboardMeasureValueFilters(widget, widgetAwareDashboardOtherFilters.filter(isMeasureValueFilter));
    return [
        ...dateFilters,
        ...attributeFilters,
        /**
         * Strictly speaking, there should be a resolution here that makes sure there is at most one MVF per measure.
         * This, however, is not worth the hassle: AD will not allow creating such insight, so the only way this might
         * happen is if widgetFilterOverrides have this clash (or someone created an insight manually using API directly).
         *
         * We choose to not do it here as doing it would need extension of the SPI with some getMeasures method
         * (because the catalog API cannot be used here as we do not know which dataset the given measure might come from)
         * and we do not want that extension at the moment (catalog API should still be good enough for most use cases).
         *
         * Dashboard-level MVFs and insight-level MVFs are sent to execution together (logical AND). Incompatible
         * dashboard MVFs (referencing metrics not in the widget) are silently ignored by the backend, so no
         * compatibility check is performed at execution time.
         */
        ...dashboardMeasureValueFilters,
        ...effectiveInsightFilters.filter(isMeasureValueFilter),
        // nothing to resolve for ranking filters
        ...insightRankingFilters,
    ];
}
function* queryWithoutInsight(widget) {
    const widgetAwareDashboardFiltersSelector = selectAllFiltersForWidgetByRef(widget.ref);
    const [widgetAwareDashboardCommonDateFilters, widgetAwareDashboardOtherFilters] = yield select(widgetAwareDashboardFiltersSelector);
    const supportsMultipleDateFilters = yield select(selectSupportsMultipleDateFilters);
    const [dateFilters, attributeFilters] = yield all([
        call(selectResolvedDateFilters, widget, widgetAwareDashboardCommonDateFilters.filter(isDateFilter), widgetAwareDashboardOtherFilters.filter(isDateFilter), supportsMultipleDateFilters),
        call(getResolvedAttributeFilters, widget, widgetAwareDashboardOtherFilters.filter(isAttributeFilter)),
    ]);
    const dashboardMeasureValueFilters = resolveDashboardMeasureValueFilters(widget, widgetAwareDashboardOtherFilters.filter(isMeasureValueFilter));
    return [...dateFilters, ...attributeFilters, ...dashboardMeasureValueFilters];
}
function* queryService(ctx, query) {
    const { payload: { widgetRef, insight }, correlationId, } = query;
    const widgetSelector = selectFilterableWidgetByRef(widgetRef);
    const widget = yield select(widgetSelector);
    if (!widget) {
        throw invalidQueryArguments(ctx, `Widget with ref ${objRefToString(widgetRef)} does not exist on the dashboard`, correlationId);
    }
    if (insight) {
        return yield call(queryWithInsight, ctx, widget, insight);
    }
    else {
        if (isInsightWidget(widget)) {
            const insightRef = widget.insight;
            const insightSelector = selectInsightByRef(insightRef);
            const linkedInsight = yield select(insightSelector);
            if (!linkedInsight) {
                throw invalidQueryArguments(ctx, `Insight with ref ${objRefToString(insightRef)} does not exist on the dashboard`, correlationId);
            }
            return yield call(queryWithInsight, ctx, widget, linkedInsight);
        }
        else {
            return yield call(queryWithoutInsight, widget);
        }
    }
}
