// (C) 2021-2026 GoodData Corporation
import { areObjRefsEqual, attributeElementsCount, dashboardFilterLocalIdentifier, dashboardFilterObjRef, isDashboardAttributeFilter, isDashboardDateFilter, isDashboardTextAttributeFilter, isSingleSelectionFilter, } from "@gooddata/sdk-model";
import { isFilterTypeCompatibleWithSelectionType } from "../../model/commandHandlers/dashboard/common/attributeFilterSelectionTypeCompatibility.js";
/**
 * Creates a mapping of attribute filter configs by their local identifiers.
 *
 * @param attributeFilterConfigs - Attribute filter configurations
 * @returns Map with local identifiers as keys and attribute filter configs as values
 * @internal
 */
function createAttributeFilterConfigsMap(attributeFilterConfigs) {
    return (attributeFilterConfigs?.reduce((acc, config) => {
        acc.set(config.localIdentifier, config);
        return acc;
    }, new Map()) ??
        new Map());
}
/**
 * Creates a mapping of date filter configs by their date data set references.
 *
 * @param dateFilterConfigs - Date filter configurations
 * @returns Map with date data set references as keys and date filter configs as values
 * @internal
 */
function createDateFilterConfigsMap(dateFilterConfigs) {
    return (dateFilterConfigs?.reduce((acc, config) => {
        acc.set(config.dateDataSet, config);
        return acc;
    }, new Map()) ??
        new Map());
}
/**
 * Finds a matching filter to merge from the filters to merge collection.
 *
 * @param originalFilter - The original filter
 * @param filtersToMerge - Collection of filters to merge
 * @returns The matching filter to merge, if any
 */
function findMatchingFilterToMerge(originalFilter, filtersToMerge) {
    // Special case for common date filters (without dataSet)
    if (isDashboardDateFilter(originalFilter) && !originalFilter.dateFilter.dataSet) {
        // Match by localIdentifier for common date filters
        return filtersToMerge.find((filter) => isDashboardDateFilter(filter) &&
            !filter.dateFilter.dataSet &&
            filter.dateFilter.localIdentifier === originalFilter.dateFilter.localIdentifier);
    }
    // Standard case for other filters
    const originalDisplayForm = dashboardFilterObjRef(originalFilter);
    if (!originalDisplayForm) {
        return undefined;
    }
    return filtersToMerge.find((filter) => {
        const displayForm = dashboardFilterObjRef(filter);
        return displayForm && areObjRefsEqual(displayForm, originalDisplayForm);
    });
}
/**
 * Identifies filters in filtersToMerge that have no corresponding filter in originalFilters.
 *
 * @param originalFilters - Original filters from the dashboard
 * @param filtersToMerge - Filters to merge into the dashboard
 * @returns Array of validation results for missing filters
 */
function findMissingFilters(originalFilters, filtersToMerge) {
    // Create a list of all original filter display forms
    const originalDisplayForms = originalFilters
        .map((filter) => dashboardFilterObjRef(filter))
        .filter((ref) => ref !== undefined);
    // Find filters that don't match any original filter
    return filtersToMerge
        .filter((filter) => {
        const displayForm = dashboardFilterObjRef(filter);
        if (!displayForm) {
            return false;
        }
        // Check if this display form exists in original filters
        return !originalDisplayForms.some((originalRef) => areObjRefsEqual(displayForm, originalRef));
    })
        .map((filter) => ({
        filter,
        error: "cannot-apply-missing-filter",
    }));
}
/**
 * Validates if an attribute filter can be applied based on its configuration.
 *
 * @param originalFilter - Original attribute filter
 * @param filterToMerge - Attribute filter to merge
 * @param filterConfig - Filter configuration
 * @param missingAttributeFilterLocalIdentifiers - List of local identifiers of missing attribute filters
 * @returns Array of validation results
 */
function validateAttributeFilter(originalFilter, filterToMerge, filterConfig, missingAttributeFilterLocalIdentifiers) {
    const validationResults = [];
    if (filterConfig?.mode === "hidden") {
        validationResults.push({
            filter: filterToMerge,
            error: "cannot-apply-hidden",
        });
    }
    else if (filterConfig?.mode === "readonly") {
        validationResults.push({
            filter: filterToMerge,
            error: "cannot-apply-readonly",
        });
    }
    else if (originalFilter.attributeFilter.selectionMode === "single" &&
        attributeElementsCount(filterToMerge.attributeFilter.attributeElements) > 1) {
        validationResults.push({
            filter: filterToMerge,
            error: "cannot-apply-multi-to-single",
        });
    }
    if (filterToMerge.attributeFilter.filterElementsBy?.some((filter) => missingAttributeFilterLocalIdentifiers.includes(filter.filterLocalIdentifier))) {
        validationResults.push({
            filter: filterToMerge,
            error: "parent-filter-is-missing",
        });
    }
    return validationResults;
}
/**
 * Modifies a multi-value attribute filter to use only the first element.
 *
 * @param filter - Attribute filter to modify
 * @returns Modified filter with only the first element
 */
function limitAttributeFilterToFirstElement(filter) {
    const result = {
        ...filter,
        attributeFilter: {
            ...filter.attributeFilter,
            selectionMode: "single",
        },
    };
    const elements = filter.attributeFilter.attributeElements;
    if ("values" in elements && Array.isArray(elements.values)) {
        result.attributeFilter.attributeElements = {
            ...elements,
            values: elements.values.slice(0, 1),
        };
    }
    else if ("uris" in elements && Array.isArray(elements.uris)) {
        result.attributeFilter.attributeElements = {
            ...elements,
            uris: elements.uris.slice(0, 1),
        };
    }
    return result;
}
// remove invalid parent filter links to not hit invariant error in useParentFilters hook
function removeInvalidParentFilterLinks(filter, missingAttributeFilterLocalIdentifiers) {
    return {
        ...filter,
        attributeFilter: {
            ...filter.attributeFilter,
            filterElementsBy: filter.attributeFilter.filterElementsBy?.filter((parentFilter) => !missingAttributeFilterLocalIdentifiers.includes(parentFilter.filterLocalIdentifier)),
        },
    };
}
/**
 * Determines the appropriate filter to use based on validation results.
 *
 * @param originalFilter - Original filter from the dashboard
 * @param filterToMerge - Filter to merge
 * @param validationResults - Validation results for this filter pair
 * @param missingAttributeFilterLocalIdentifiers - List of local identifiers of missing attribute filters
 * @returns The filter to use in the merged result
 */
function determineFilterToUse(originalFilter, filterToMerge, validationResults, missingAttributeFilterLocalIdentifiers) {
    // If no validation issues, use the filter to merge
    if (validationResults.length === 0) {
        return filterToMerge;
    }
    // Handle special case of multi-to-single validation
    if (isDashboardAttributeFilter(originalFilter) &&
        isDashboardAttributeFilter(filterToMerge) &&
        validationResults.some((result) => result.error === "cannot-apply-multi-to-single")) {
        return limitAttributeFilterToFirstElement(filterToMerge);
    }
    if (isDashboardAttributeFilter(filterToMerge) &&
        validationResults.some((result) => result.error === "parent-filter-is-missing")) {
        return removeInvalidParentFilterLinks(filterToMerge, missingAttributeFilterLocalIdentifiers);
    }
    // For all other validation issues, keep the original filter
    return originalFilter;
}
/**
 * Merges an attribute filter from the original filter list with a corresponding filter to merge.
 *
 * @param originalFilter - Original attribute filter
 * @param filterToMerge - Attribute filter to merge, if found
 * @param attributeFilterConfigs - Attribute filter configurations
 * @param missingAttributeFilterLocalIdentifiers - List of local identifiers of missing attribute filters
 * @returns Object containing the merged filter and validation results
 */
function mergeAttributeFilter(originalFilter, filterToMerge, attributeFilterConfigs, missingAttributeFilterLocalIdentifiers) {
    // No matching filter found, keep original
    if (!filterToMerge) {
        return {
            mergedFilter: originalFilter,
            validationResults: [],
        };
    }
    // Find the filter configuration using the local identifier
    const filterLocalIdentifier = originalFilter.attributeFilter.localIdentifier;
    const filterConfigsMap = createAttributeFilterConfigsMap(attributeFilterConfigs);
    const filterConfig = filterLocalIdentifier ? filterConfigsMap.get(filterLocalIdentifier) : undefined;
    // Check hidden/readonly mode before type branching
    if (filterConfig?.mode === "hidden") {
        return {
            mergedFilter: originalFilter,
            validationResults: [{ filter: filterToMerge, error: "cannot-apply-hidden" }],
        };
    }
    if (filterConfig?.mode === "readonly") {
        return {
            mergedFilter: originalFilter,
            validationResults: [{ filter: filterToMerge, error: "cannot-apply-readonly" }],
        };
    }
    const overrideType = isDashboardAttributeFilter(filterToMerge) ? "list" : "text";
    const singleSelectionDefault = isSingleSelectionFilter(originalFilter) ? "list" : undefined;
    if (!isFilterTypeCompatibleWithSelectionType(overrideType, filterConfig?.selectionType, singleSelectionDefault)) {
        return {
            mergedFilter: originalFilter,
            validationResults: [
                {
                    filter: filterToMerge,
                    error: "cannot-apply-incompatible-selection-type",
                },
            ],
        };
    }
    // For list-type overrides, run list-specific validations (mode, multi-to-single, parent filters)
    if (isDashboardAttributeFilter(filterToMerge)) {
        const validationResults = validateAttributeFilter(originalFilter, filterToMerge, filterConfig, missingAttributeFilterLocalIdentifiers);
        const mergedFilter = determineFilterToUse(originalFilter, filterToMerge, validationResults, missingAttributeFilterLocalIdentifiers);
        return {
            mergedFilter,
            validationResults,
        };
    }
    // Text-type override passed selectionType check — apply it
    return {
        mergedFilter: filterToMerge,
        validationResults: [],
    };
}
/**
 * Merges a text attribute filter, checking selectionType compatibility.
 *
 * @param originalFilter - Original text attribute filter
 * @param filterToMerge - Filter to merge, if found
 * @param attributeFilterConfigs - Attribute filter configurations
 * @returns Object containing the merged filter and validation results
 */
function mergeTextAttributeFilter(originalFilter, filterToMerge, attributeFilterConfigs) {
    if (!filterToMerge) {
        return {
            mergedFilter: originalFilter,
            validationResults: [],
        };
    }
    const filterLocalIdentifier = dashboardFilterLocalIdentifier(originalFilter);
    const filterConfigsMap = createAttributeFilterConfigsMap(attributeFilterConfigs);
    const filterConfig = filterLocalIdentifier ? filterConfigsMap.get(filterLocalIdentifier) : undefined;
    // Check hidden/readonly mode
    if (filterConfig?.mode === "hidden") {
        return {
            mergedFilter: originalFilter,
            validationResults: [{ filter: filterToMerge, error: "cannot-apply-hidden" }],
        };
    }
    if (filterConfig?.mode === "readonly") {
        return {
            mergedFilter: originalFilter,
            validationResults: [{ filter: filterToMerge, error: "cannot-apply-readonly" }],
        };
    }
    const overrideType = isDashboardTextAttributeFilter(filterToMerge) ? "text" : "list";
    if (!isFilterTypeCompatibleWithSelectionType(overrideType, filterConfig?.selectionType)) {
        return {
            mergedFilter: originalFilter,
            validationResults: [
                {
                    filter: filterToMerge,
                    error: "cannot-apply-incompatible-selection-type",
                },
            ],
        };
    }
    return {
        mergedFilter: filterToMerge,
        validationResults: [],
    };
}
/**
 * Merges a date filter from the original filter list with a corresponding filter to merge.
 *
 * @param originalFilter - Original date filter
 * @param filterToMerge - Date filter to merge, if found
 * @param dateFilterConfig - Common date filter configuration
 * @param dateFilterConfigs - Date filter configurations by dataset
 * @returns Object containing the merged filter and validation results
 */
function mergeDateFilter(originalFilter, filterToMerge, dateFilterConfig, dateFilterConfigs) {
    // No matching filter found, keep original
    if (!filterToMerge || !isDashboardDateFilter(filterToMerge)) {
        return {
            mergedFilter: originalFilter,
            validationResults: [],
        };
    }
    const isCommonDateFilter = !originalFilter.dateFilter.dataSet;
    const validationResults = [];
    // Apply the appropriate validation rules based on filter type
    if (isCommonDateFilter) {
        // For common date filters (without dataSet)
        if (dateFilterConfig?.mode === "hidden") {
            validationResults.push({
                filter: filterToMerge,
                error: "cannot-apply-hidden",
            });
        }
        else if (dateFilterConfig?.mode === "readonly") {
            validationResults.push({
                filter: filterToMerge,
                error: "cannot-apply-readonly",
            });
        }
    }
    else {
        // For date filters with dimensions (with dataSet)
        const dateDataSet = originalFilter.dateFilter.dataSet;
        if (dateDataSet) {
            const dateFilterConfigsMap = createDateFilterConfigsMap(dateFilterConfigs || []);
            const filterConfig = dateFilterConfigsMap.get(dateDataSet);
            if (filterConfig?.config.mode === "hidden") {
                validationResults.push({
                    filter: filterToMerge,
                    error: "cannot-apply-hidden",
                });
            }
            else if (filterConfig?.config.mode === "readonly") {
                validationResults.push({
                    filter: filterToMerge,
                    error: "cannot-apply-readonly",
                });
            }
        }
    }
    // If there are validation issues, keep the original filter
    // Otherwise use the filter to merge
    return {
        mergedFilter: validationResults.length > 0 ? originalFilter : filterToMerge,
        validationResults,
    };
}
/**
 * Merges dashboard filters according to configuration rules.
 *
 * This function validates and merges dashboard filters from one context into another,
 * respecting filter configuration settings like read-only or hidden status.
 *
 * @param originalFilters - Original filters from the dashboard
 * @param filtersToMerge - Filters to be merged into the dashboard
 * @param config - Dashboard filter configuration
 * @returns Object containing merged filters and validation results
 * @internal
 */
export function mergeFilterContextFilters(originalFilters, filtersToMerge, config) {
    // Find filters that are missing in the dashboard
    const missingFilterValidationResults = findMissingFilters(originalFilters, filtersToMerge);
    const missingAttributeFilterLocalIdentifiers = missingFilterValidationResults
        .map((result) => isDashboardAttributeFilter(result.filter)
        ? result.filter.attributeFilter.localIdentifier
        : undefined)
        .filter((localIdentifier) => localIdentifier !== undefined);
    // Process each original filter
    const mergeResults = originalFilters.map((originalFilter) => {
        // Find matching filter to merge
        const filterToMerge = findMatchingFilterToMerge(originalFilter, filtersToMerge);
        if (isDashboardAttributeFilter(originalFilter)) {
            return mergeAttributeFilter(originalFilter, filterToMerge, config.attributeFilterConfigs, missingAttributeFilterLocalIdentifiers);
        }
        else if (isDashboardDateFilter(originalFilter)) {
            return mergeDateFilter(originalFilter, filterToMerge, config.dateFilterConfig, config.dateFilterConfigs);
        }
        else if (isDashboardTextAttributeFilter(originalFilter)) {
            return mergeTextAttributeFilter(originalFilter, filterToMerge, config.attributeFilterConfigs);
        }
        else {
            // For unknown filter types, just keep the original with no validation
            return {
                mergedFilter: originalFilter,
                validationResults: [],
            };
        }
    });
    // Combine all validation results
    const validationResults = [
        ...missingFilterValidationResults,
        ...mergeResults.flatMap((result) => result.validationResults),
    ];
    // Extract the merged filters
    const mergedFilters = mergeResults.map((result) => result.mergedFilter);
    return {
        mergedFilters,
        validationResults,
    };
}
//# sourceMappingURL=dashboardFilterContextValidation.js.map