// (C) 2020-2026 GoodData Corporation
import stringify from "json-stable-stringify";
import { groupBy } from "lodash-es";
import { all, call, select } from "redux-saga/effects";
import { areObjRefsEqual, dashboardAttributeFilterItemDisplayForm, filterAttributeElements, filterObjRef, idRef, insightId, isArbitraryAttributeFilter, isAttributeDescriptor, isAttributeElementsByValue, isDashboardArbitraryAttributeFilter, isDashboardAttributeFilter, isDashboardTextAttributeFilter, isNegativeAttributeFilter, isTextAttributeFilter, } from "@gooddata/sdk-model";
import { getAttributeIdentifiersPlaceholdersFromUrl, getDashboardAttributeFilterPlaceholdersFromUrl, getInsightAttributeFilterPlaceholdersFromUrl, } from "@gooddata/sdk-model/internal";
import { isDrillIntersectionAttributeItem, } from "@gooddata/sdk-ui";
import { invalidArgumentsProvided } from "../../events/general.js";
import { queryWidgetFilters } from "../../queries/widgets.js";
import { query } from "../../store/_infra/queryCall.js";
import { selectAllCatalogDisplayFormsMap, selectCatalogDateAttributes, } from "../../store/catalog/catalogSelectors.js";
import { selectInsightByRef } from "../../store/insights/insightsSelectors.js";
import { selectDashboardId } from "../../store/meta/metaSelectors.js";
import { selectAttributeFilterConfigsOverrides } from "../../store/tabs/attributeFilterConfigs/attributeFilterConfigsSelectors.js";
import { selectFilterContextAttributeFilterItems } from "../../store/tabs/filterContext/filterContextSelectors.js";
import { selectAnalyticalWidgetByRef } from "../../store/tabs/layout/layoutSelectors.js";
import { DRILL_TO_URL_PLACEHOLDER } from "../../types/drillTypes.js";
import { getElementTitle, getElementsSecondaryTitles } from "./getElementTitle.js";
export function* loadElementTitle(dfRef, dfIdentifier, attrElementUri, ctx) {
    const elementTitle = yield call(getElementTitle, ctx.workspace, dfRef, attrElementUri, ctx);
    return {
        identifier: dfIdentifier,
        elementTitle,
    };
}
export function* loadSecondaryElementTitles(dfRef, attrElementValues, ctx) {
    const elementsTitles = yield call(getElementsSecondaryTitles, ctx.workspace, dfRef, attrElementValues, ctx);
    return elementsTitles;
}
function isInRefList(list, ref) {
    return list.some((itemRef) => areObjRefsEqual(itemRef, ref));
}
function findDrillIntersectionAttributeHeaderItem(drillIntersectionElements, attributeRef) {
    const intersectionForAttribute = drillIntersectionElements.find(({ header }) => isAttributeDescriptor(header) && areObjRefsEqual(attributeRef, header.attributeHeader.formOf.ref));
    if (intersectionForAttribute && isDrillIntersectionAttributeItem(intersectionForAttribute.header)) {
        return intersectionForAttribute.header.attributeHeaderItem;
    }
    return undefined;
}
export function* splitDFToLoadingAndMapping(attributesDisplayForms, ctx) {
    if (ctx.backend.capabilities.supportsElementUris) {
        return {
            displayFormForValueLoad: attributesDisplayForms,
            displayFormsWithKnownValues: [],
        };
    }
    // in tiger there are no uris for attribute values, only primary values
    // we can't call collectLabelElements for date attributes because there are no date "elements", but we can use this values directly.
    const dateAttributes = yield select(selectCatalogDateAttributes);
    const dateAttributeRefs = dateAttributes.map((da) => da.attribute.ref);
    const { true: displayFormsWithKnownValues = [], false: displayFormForValueLoad = [] } = groupBy(attributesDisplayForms, (df) => isInRefList(dateAttributeRefs, df.attribute));
    return {
        displayFormsWithKnownValues,
        displayFormForValueLoad,
    };
}
export function* loadAttributeElementsForDrillIntersection(drillIntersectionElements, attributesDisplayForms, ctx) {
    const splitDisplayForms = yield call(splitDFToLoadingAndMapping, attributesDisplayForms, ctx);
    const { displayFormsWithKnownValues, displayFormForValueLoad } = splitDisplayForms;
    const mappedElements = displayFormsWithKnownValues.reduce((acc, { id: dfIdentifier, attribute }) => {
        const attributeHeaderItem = findDrillIntersectionAttributeHeaderItem(drillIntersectionElements, attribute);
        if (!attributeHeaderItem) {
            return acc;
        }
        acc.push({
            identifier: dfIdentifier,
            elementTitle: attributeHeaderItem.uri,
        });
        return acc;
    }, []);
    const loadedElement = yield all(displayFormForValueLoad.reduce((acc, displayForm) => {
        const { id: dfIdentifier, attribute, ref: dfRef } = displayForm;
        const attributeHeaderItem = findDrillIntersectionAttributeHeaderItem(drillIntersectionElements, attribute);
        if (!attributeHeaderItem) {
            return acc;
        }
        acc.push(call(loadElementTitle, dfRef, dfIdentifier, attributeHeaderItem.uri, ctx));
        return acc;
    }, []));
    return [...mappedElements, ...loadedElement];
}
const encodeParameterIfSet = (parameter) => parameter === null || parameter === undefined ? parameter : encodeURIComponent(parameter);
export function getAttributeDisplayForms(projectId, objRefs, ctx) {
    return ctx.backend.workspace(projectId).attributes().getAttributeDisplayForms(objRefs);
}
export function* getAttributeIdentifiersReplacements(url, drillIntersectionElements, ctx) {
    const attributeIdentifiersPlaceholders = getAttributeIdentifiersPlaceholdersFromUrl(url);
    if (attributeIdentifiersPlaceholders.length === 0) {
        return [];
    }
    const displayForms = yield call(getAttributeDisplayForms, ctx.workspace, attributeIdentifiersPlaceholders.map((placeholder) => idRef(placeholder.identifier)), ctx);
    const elements = yield call(loadAttributeElementsForDrillIntersection, drillIntersectionElements, displayForms, ctx);
    return attributeIdentifiersPlaceholders.map(({ placeholder: toBeReplaced, identifier, toBeEncoded }) => {
        const elementTitle = elements.find((element) => element.identifier === identifier)?.elementTitle;
        const replacement = toBeEncoded ? encodeParameterIfSet(elementTitle) : elementTitle;
        return {
            toBeReplaced,
            replacement: replacement,
        };
    });
}
function* resolveDashboardAttributeFilterReplacement({ placeholder: toBeReplaced, ref }, attributeFilters, catalogDisplayForms, attributeFilterConfigs, ctx) {
    // Check for text filters (arbitrary/match) first — they use different value structures.
    const textFilter = attributeFilters.find((filter) => {
        if (!isDashboardTextAttributeFilter(filter)) {
            return false;
        }
        const df = catalogDisplayForms.get(dashboardAttributeFilterItemDisplayForm(filter));
        return df && areObjRefsEqual(idRef(df.id), ref);
    });
    if (textFilter && isDashboardTextAttributeFilter(textFilter)) {
        const parsedFilter = stringifyTextFilterSelection(textFilter);
        const replacement = encodeParameterIfSet(parsedFilter);
        return {
            toBeReplaced,
            replacement: replacement,
        };
    }
    // Standard (element-based) attribute filters
    const standardFilters = attributeFilters.filter(isDashboardAttributeFilter);
    let usedFilter = standardFilters.find((filter) => {
        const df = catalogDisplayForms.get(filter.attributeFilter.displayForm);
        return df && areObjRefsEqual(idRef(df.id), ref);
    });
    let attributeElementsValues = [];
    if (usedFilter) {
        const elements = usedFilter?.attributeFilter.attributeElements;
        attributeElementsValues = isAttributeElementsByValue(elements)
            ? elements.values
            : (elements?.uris ?? []);
    }
    else {
        const usedConfig = attributeFilterConfigs.find((config) => {
            return config.displayAsLabel && areObjRefsEqual(config.displayAsLabel, ref);
        });
        usedFilter = standardFilters.find((filter) => {
            return filter.attributeFilter.localIdentifier === usedConfig?.localIdentifier;
        });
        const elements = usedFilter?.attributeFilter.attributeElements;
        const primaryElementsValues = elements
            ? isAttributeElementsByValue(elements)
                ? elements.values
                : elements.uris
            : [];
        attributeElementsValues = (yield call(loadSecondaryElementTitles, ref, primaryElementsValues, ctx));
    }
    const isNegative = usedFilter?.attributeFilter.negativeSelection;
    const parsedFilter = usedFilter
        ? stringifyAttributeFilterSelection(attributeElementsValues, isNegative)
        : undefined;
    const replacement = encodeParameterIfSet(parsedFilter);
    return {
        toBeReplaced,
        replacement: replacement,
    };
}
export function* getDashboardAttributeFilterReplacements(url, ctx) {
    const attributeFilterPlaceholders = getDashboardAttributeFilterPlaceholdersFromUrl(url);
    if (attributeFilterPlaceholders.length === 0) {
        return [];
    }
    const attributeFilters = yield select(selectFilterContextAttributeFilterItems);
    const catalogDisplayForms = yield select(selectAllCatalogDisplayFormsMap);
    const attributeFilterConfigs = yield select(selectAttributeFilterConfigsOverrides);
    return yield all(attributeFilterPlaceholders.map((placeholder) => call(resolveDashboardAttributeFilterReplacement, placeholder, attributeFilters, catalogDisplayForms, attributeFilterConfigs, ctx)));
}
export function* getInsightAttributeFilterReplacements(url, widgetRef) {
    const attributeFilterPlaceholders = getInsightAttributeFilterPlaceholdersFromUrl(url);
    if (attributeFilterPlaceholders.length === 0) {
        return [];
    }
    const widgetFilters = yield call(query, queryWidgetFilters(widgetRef));
    const catalogDisplayForms = yield select(selectAllCatalogDisplayFormsMap);
    return attributeFilterPlaceholders.map(({ placeholder: toBeReplaced, ref }) => {
        const usedFilter = widgetFilters.find((filter) => {
            const filterRef = filterObjRef(filter);
            const df = filterRef && catalogDisplayForms.get(filterRef);
            return df && areObjRefsEqual(idRef(df.id), ref);
        });
        let parsedFilter;
        if (usedFilter && isTextAttributeFilter(usedFilter)) {
            parsedFilter = stringifyTextFilterSelection(usedFilter);
        }
        else if (usedFilter) {
            const elements = filterAttributeElements(usedFilter);
            const attributeElementsValues = isAttributeElementsByValue(elements)
                ? elements.values
                : (elements?.uris ?? []);
            const isNegative = isNegativeAttributeFilter(usedFilter);
            parsedFilter = stringifyAttributeFilterSelection(attributeElementsValues, isNegative);
        }
        const replacement = encodeParameterIfSet(parsedFilter);
        return {
            toBeReplaced,
            replacement: replacement,
        };
    });
}
const createIdentifierReplacement = (toBeReplaced, replacement = "") => ({ toBeReplaced, replacement, replaceGlobally: true });
export function* getInsightIdentifiersReplacements(customUrl, widgetRef, ctx) {
    const { workspace, clientId, dataProductId } = ctx;
    const dashboardId = yield select(selectDashboardId);
    const widget = yield select(selectAnalyticalWidgetByRef(widgetRef));
    const insight = yield select(selectInsightByRef(widget.insight));
    const replacements = [
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_PROJECT_ID, workspace),
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_WORKSPACE_ID, workspace),
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_DASHBOARD_ID, dashboardId),
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_CLIENT_ID, clientId),
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_DATA_PRODUCT_ID, dataProductId),
        createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_INSIGHT_ID, insightId(insight)),
    ];
    if (customUrl.includes(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_WIDGET_ID)) {
        return [
            ...replacements,
            createIdentifierReplacement(DRILL_TO_URL_PLACEHOLDER.DRILL_TO_URL_PLACEHOLDER_WIDGET_ID, widget.identifier),
        ];
    }
    return replacements;
}
const applyReplacements = (url, replacements) => replacements.reduce((customUrlWithReplacedPlaceholders, { toBeReplaced, replacement, replaceGlobally }) => customUrlWithReplacedPlaceholders.replace(replaceGlobally ? new RegExp(toBeReplaced, "g") : toBeReplaced, replacement), url);
export function* resolveDrillToCustomUrl(drillConfig, widgetRef, event, ctx, cmd) {
    const customUrl = drillConfig.target.url;
    const attributeIdentifiersReplacements = yield call(getAttributeIdentifiersReplacements, customUrl, event.drillContext.intersection, ctx);
    const dashboardAttributeFilterReplacements = yield call(getDashboardAttributeFilterReplacements, customUrl, ctx);
    const insightAttributeFilterReplacements = yield call(getInsightAttributeFilterReplacements, customUrl, widgetRef);
    const missingReplacement = [
        ...attributeIdentifiersReplacements,
        ...dashboardAttributeFilterReplacements,
        ...insightAttributeFilterReplacements,
    ].find(({ replacement }) => replacement === undefined);
    if (missingReplacement) {
        throw invalidArgumentsProvided(ctx, cmd, `Drill to custom URL unable to resolve missing parameter ${missingReplacement.toBeReplaced}`);
    }
    const insightIdentifiersReplacements = yield call(getInsightIdentifiersReplacements, customUrl, widgetRef, ctx);
    const replacements = [
        ...attributeIdentifiersReplacements,
        ...dashboardAttributeFilterReplacements,
        ...insightAttributeFilterReplacements,
        ...insightIdentifiersReplacements,
    ];
    return applyReplacements(customUrl, replacements);
}
function stringifyAttributeFilterSelection(selection, isNegative) {
    return `${isNegative ? "NOT_IN" : "IN"}${stringify(selection)}`;
}
/**
 * @internal
 */
export function stringifyTextFilterSelection(filter) {
    if (isDashboardArbitraryAttributeFilter(filter) || isArbitraryAttributeFilter(filter)) {
        const { values, negativeSelection } = filter.arbitraryAttributeFilter;
        return stringifyAttributeFilterSelection(values, negativeSelection ?? false);
    }
    const { literal, operator, negativeSelection } = filter.matchAttributeFilter;
    return stringifyMatchFilterSelection(literal, operator, negativeSelection ?? false);
}
const MATCH_OPERATOR_MAP = {
    contains: "CONTAINS",
    startsWith: "STARTS_WITH",
    endsWith: "ENDS_WITH",
};
function stringifyMatchFilterSelection(literal, operator, isNegative) {
    const operatorStr = MATCH_OPERATOR_MAP[operator];
    const prefix = isNegative ? `NOT_${operatorStr}` : operatorStr;
    return `${prefix}${stringify([literal])}`;
}
//# sourceMappingURL=resolveDrillToCustomUrl.js.map