// (C) 2019-2026 GoodData Corporation
import { cloneDeep, compact, isEmpty, negate, set, uniqBy, without } from "lodash-es";
import { areObjRefsEqual, bucketItems, bucketsFind, bucketsMeasures, idRef, insightBuckets, insightVisualizationType, isObjRef, isSimpleMeasure, } from "@gooddata/sdk-model";
import { BucketNames, OverTimeComparisonTypes, VisualizationTypes, } from "@gooddata/sdk-ui";
import { isBulletChart, isComboChart, isLineChart } from "@gooddata/sdk-ui-charts";
import { getTranslation } from "./translations.js";
import { subtitles, titles } from "../../locales.js";
import { ATTRIBUTE, BUCKETS, DATE, METRIC, SHOW_ON_SECONDARY_AXIS } from "../constants/bucket.js";
import { DATE_DATASET_ATTRIBUTE, } from "../interfaces/Visualization.js";
export function isDateFilter(filter) {
    return !!filter && filter.attribute === DATE_DATASET_ATTRIBUTE;
}
export function isFiltersBucketItem(filter) {
    return filter?.attribute === DATE_DATASET_ATTRIBUTE;
}
export function isAttributeFilter(filter) {
    const filterAsAttributeFilter = filter;
    return (!!filter &&
        filterAsAttributeFilter.attribute !== DATE_DATASET_ATTRIBUTE &&
        filterAsAttributeFilter.attribute !== undefined);
}
export function isMeasureValueFilter(filter) {
    if (filter === undefined || filter === null) {
        return false;
    }
    const measureValueFilter = filter;
    return !!measureValueFilter.measureLocalIdentifier || !!measureValueFilter.measureRef;
}
export function isActiveMeasureValueFilter(filter) {
    if (!isMeasureValueFilter(filter)) {
        return false;
    }
    return !!filter.condition;
}
export function isRankingFilter(filter) {
    const filterAsRankingFilter = filter;
    return (!!filter &&
        typeof filterAsRankingFilter.measure === "string" &&
        typeof filterAsRankingFilter.operator === "string" &&
        typeof filterAsRankingFilter.value === "number");
}
function getDimensionalityRef(localIdentifier, oldAttributeBucketItems) {
    const fromOld = oldAttributeBucketItems.find((item) => item.localIdentifier === localIdentifier);
    // Resolve using old reference point snapshot (the moment when attribute was still in buckets).
    const bucketItem = fromOld;
    // MVF dimensionality should be expressed using ObjRefs.
    // - For attribute bucket items, display form ref is stored on dfRef
    // - For date bucket items, the date dataset ref is stored on dateDatasetRef
    return bucketItem?.dfRef ?? bucketItem?.dateDatasetRef;
}
function transformMeasureValueFilterMeasureToRef(filter, measureBucketItems, oldMeasureBucketItems) {
    // Transform measure value filter: if measure is referenced by localId but not in current buckets,
    // check previous buckets and transform to ref for simple measures
    if (!filter.measureLocalIdentifier || filter.measureRef) {
        return filter;
    }
    const measureLocalId = filter.measureLocalIdentifier;
    // Check if measure exists in current buckets
    const currentMeasure = measureBucketItems.find((item) => item.localIdentifier === measureLocalId);
    if (currentMeasure) {
        return filter;
    }
    // Measure not in current buckets, check previous buckets
    const previousMeasure = oldMeasureBucketItems.find((item) => item.localIdentifier === measureLocalId);
    if (!previousMeasure) {
        return filter;
    }
    // Check if it's a simple measure (has attribute, no aggregation, no master local identifier)
    const isNotSimpleMeasure = !previousMeasure.attribute ||
        !!previousMeasure.aggregation ||
        !!previousMeasure.masterLocalIdentifier;
    if (isNotSimpleMeasure) {
        return filter;
    }
    // Transform to use measureRef instead of measureLocalIdentifier
    const { measureLocalIdentifier: _measureLocalIdentifier, ...restFilter } = filter;
    return {
        ...restFilter,
        measureRef: idRef(previousMeasure.attribute, "measure"),
    };
}
function transformMeasureValueFilterDimensionalityToRefs(filter, oldAttributeBucketItems, dimensionalityLocalIdsToTransform) {
    if (dimensionalityLocalIdsToTransform.size === 0) {
        return filter;
    }
    if (!filter.dimensionality || filter.dimensionality.length === 0) {
        return filter;
    }
    const resolvedDimensionality = filter.dimensionality.map((dimensionalityItem) => {
        if (isObjRef(dimensionalityItem)) {
            return dimensionalityItem;
        }
        if (!dimensionalityLocalIdsToTransform.has(dimensionalityItem)) {
            return dimensionalityItem;
        }
        const resolved = getDimensionalityRef(dimensionalityItem, oldAttributeBucketItems);
        // If we cannot resolve, keep the original local identifier. The subsequent sanitization pass
        // will drop such filter if the local identifier is not valid in the current reference point.
        return resolved ?? dimensionalityItem;
    });
    return {
        ...filter,
        dimensionality: resolvedDimensionality,
    };
}
function getMeasureValueFilterDimensionalityLocalIdsToTransform(filter, attributeBucketItems) {
    const localIdsToTransform = new Set();
    if (!filter.dimensionality || filter.dimensionality.length === 0) {
        return localIdsToTransform;
    }
    for (const item of filter.dimensionality) {
        if (isObjRef(item)) {
            continue;
        }
        const existsInCurrentBuckets = attributeBucketItems.some((attributeBucketItem) => attributeBucketItem.localIdentifier === item);
        if (!existsInCurrentBuckets) {
            localIdsToTransform.add(item);
        }
    }
    return localIdsToTransform;
}
function sanitizeMeasureValueFilter(filter, attributeBucketItems, measureBucketItems, enableImprovedAdFilters) {
    if (attributeBucketItems.length === 0 && !enableImprovedAdFilters) {
        return false;
    }
    // Validate dimensionality - each item based on its type
    const hasValidDimensionality = !filter.dimensionality ||
        filter.dimensionality.every((item) => {
            if (isObjRef(item)) {
                // ObjRef (catalog attribute) - valid only when the feature flag is enabled
                return enableImprovedAdFilters;
            }
            else {
                // Local identifier - must exist in attribute bucket items
                return attributeBucketItems.some((attributeBucketItem) => attributeBucketItem.localIdentifier === item);
            }
        });
    if (!hasValidDimensionality) {
        return false;
    }
    // When enableImprovedAdFilters is true, allow measure filters even if measure is not in buckets
    if (enableImprovedAdFilters && filter.measureRef !== undefined) {
        return true;
    }
    return measureBucketItems.some((measureBucketItem) => measureBucketItem.localIdentifier === filter.measureLocalIdentifier);
}
export function sanitizeFilters(newReferencePoint, enableImprovedAdFilters = false, oldReferencePoint) {
    const attributeBucketItems = getAllAttributeItems(newReferencePoint.buckets);
    const oldAttributeBucketItems = getAllAttributeItems(oldReferencePoint.buckets);
    const measureBucketItems = getAllMeasureItems(newReferencePoint.buckets);
    const oldMeasureBucketItems = getAllMeasureItems(oldReferencePoint.buckets);
    newReferencePoint.filters = newReferencePoint.filters || {
        localIdentifier: "filters",
        items: [],
    };
    // transformation
    const transformedFilterItems = newReferencePoint.filters.items.map((filterBucketItem) => {
        const filter = filterBucketItem.filters?.[0];
        if (isMeasureValueFilter(filter) && enableImprovedAdFilters) {
            // Transform measure ref: migrate from localId to ref when measure is removed from buckets
            const filterWithTransformedMeasure = transformMeasureValueFilterMeasureToRef(filter, measureBucketItems, oldMeasureBucketItems);
            // Transform dimensionality
            const dimensionalityLocalIdsToTransform = getMeasureValueFilterDimensionalityLocalIdsToTransform(filterWithTransformedMeasure, attributeBucketItems);
            const filterWithTransformedDimensionality = transformMeasureValueFilterDimensionalityToRefs(filterWithTransformedMeasure, oldAttributeBucketItems, dimensionalityLocalIdsToTransform);
            if (filterWithTransformedDimensionality !== filter) {
                return {
                    ...filterBucketItem,
                    filters: [filterWithTransformedDimensionality],
                };
            }
        }
        return filterBucketItem;
    });
    //sanitization
    const filteredFilters = transformedFilterItems.filter((filterBucketItem) => {
        const filter = filterBucketItem.filters?.[0];
        if (!filter) {
            return false;
        }
        if (isAttributeFilter(filter) || isDateFilter(filter)) {
            if (filterBucketItem.autoCreated === false) {
                return true;
            }
            return attributeBucketItems.some((attributeBucketItem) => attributeBucketItem.attribute === filter.attribute);
        }
        else if (isMeasureValueFilter(filter)) {
            return sanitizeMeasureValueFilter(filter, attributeBucketItems, measureBucketItems, enableImprovedAdFilters);
        }
        else if (isRankingFilter(filter)) {
            if (attributeBucketItems.length === 0) {
                return false;
            }
            const hasValidMeasure = measureBucketItems.some((measureBucketItem) => measureBucketItem.localIdentifier === filter.measure);
            const hasValidAttributes = !filter.attributes ||
                filter.attributes.every((localIdentifier) => attributeBucketItems.some((attributeBucketItem) => attributeBucketItem.localIdentifier === localIdentifier));
            return hasValidMeasure && hasValidAttributes;
        }
        return false;
    });
    return {
        ...newReferencePoint,
        filters: {
            ...newReferencePoint.filters,
            items: filteredFilters,
        },
    };
}
export function isDerivedBucketItem(measureItem) {
    return !!measureItem.masterLocalIdentifier;
}
export function isArithmeticBucketItem(bucketItem) {
    return !!bucketItem.operandLocalIdentifiers;
}
function isDerivedOfTypeBucketItem(measureItem, derivedType) {
    if (!isDerivedBucketItem(measureItem)) {
        return false;
    }
    return measureItem.overTimeComparisonType === derivedType;
}
function findDerivedTypesReferencedByArithmeticMeasure(measure, allMeasures, visitedMeasures) {
    return measure.operandLocalIdentifiers?.reduce((types, operandIdentifier) => {
        if (operandIdentifier === null || visitedMeasures.has(operandIdentifier)) {
            return types;
        }
        const operand = findMeasureByLocalIdentifier(operandIdentifier, allMeasures);
        if (operand === undefined) {
            return types;
        }
        if (isArithmeticBucketItem(operand)) {
            visitedMeasures.add(operandIdentifier);
            findDerivedTypesReferencedByArithmeticMeasure(operand, allMeasures, visitedMeasures)?.forEach((type) => types.add(type));
        }
        else if (isDerivedBucketItem(operand) &&
            operand.overTimeComparisonType &&
            !types.has(operand.overTimeComparisonType)) {
            types.add(operand.overTimeComparisonType);
        }
        return types;
    }, new Set());
}
/**
 * Get array of unique over time comparison types used in ancestors of the provided arithmetic measure.
 *
 * @param measure - the (possibly) arithmetic measure
 * @param buckets - all buckets
 * @returns empty array if there are no derived measures in the arithmetic measure ancestors, empty array if provided
 * measure is not arithmetic, array of unique {@link OverTimeComparisonType} of derived ancestor measures found in arithmetic
 * measure tree.
 */
export function getDerivedTypesFromArithmeticMeasure(measure, buckets) {
    if (!isArithmeticBucketItem(measure)) {
        return [];
    }
    const allMeasures = buckets.flatMap((bucket) => bucket.items);
    const overTimeComparisonTypes = findDerivedTypesReferencedByArithmeticMeasure(measure, allMeasures, new Set());
    return Array.from(overTimeComparisonTypes ?? []);
}
export function filterOutDerivedMeasures(measures) {
    return measures.filter((measure) => !isDerivedBucketItem(measure));
}
function isArithmeticMeasureFromDerived(measure, buckets) {
    return getDerivedTypesFromArithmeticMeasure(measure, buckets).length > 0;
}
export function filterOutArithmeticMeasuresFromDerived(measures, buckets) {
    return measures.filter((measure) => !isArithmeticMeasureFromDerived(measure, buckets));
}
function isArithmeticMeasureFromDerivedOfTypeOnly(measure, buckets, derivedType) {
    const arithmeticMeasureDerivedTypes = getDerivedTypesFromArithmeticMeasure(measure, buckets);
    return arithmeticMeasureDerivedTypes.length === 1 && arithmeticMeasureDerivedTypes[0] === derivedType;
}
export function keepOnlyMasterAndDerivedMeasuresOfType(measures, derivedType) {
    return measures.filter((measure) => !isDerivedBucketItem(measure) || isDerivedOfTypeBucketItem(measure, derivedType));
}
export function filterOutIncompatibleArithmeticMeasures(measures, buckets, derivedOfTypeToKeep) {
    return measures.filter((measure) => !isArithmeticBucketItem(measure) ||
        !isArithmeticMeasureFromDerived(measure, buckets) ||
        isArithmeticMeasureFromDerivedOfTypeOnly(measure, buckets, derivedOfTypeToKeep));
}
export function isDateBucketItem(bucketItem) {
    return bucketItem?.type === DATE;
}
export const isNotDateBucketItem = negate(isDateBucketItem);
export function getDateFilter(filtersBucket) {
    if (!filtersBucket) {
        return null;
    }
    const dateFiltersInclEmpty = filtersBucket.items.flatMap((filterItem) => {
        const filters = filterItem.filters ?? [];
        return filters.find(isDateFilter);
    });
    const dateFilters = compact(dateFiltersInclEmpty);
    return dateFilters.length ? dateFilters[0] : null;
}
export function getComparisonTypeFromFilters(filtersBucket) {
    if (isEmpty(filtersBucket)) {
        return OverTimeComparisonTypes.NOTHING;
    }
    const dateFilter = getDateFilter(filtersBucket);
    return !isEmpty(dateFilter) && dateFilter.overTimeComparisonType
        ? dateFilter.overTimeComparisonType
        : OverTimeComparisonTypes.NOTHING;
}
function bucketSupportsSubtitle(visualizationType, bucketLocalIdentifier) {
    switch (visualizationType) {
        case VisualizationTypes.HEADLINE:
            return true;
        case VisualizationTypes.SCATTER:
            return !(bucketLocalIdentifier === BucketNames.ATTRIBUTE ||
                bucketLocalIdentifier === BucketNames.SEGMENT);
        case VisualizationTypes.BUBBLE:
            return bucketLocalIdentifier !== BucketNames.VIEW;
        case VisualizationTypes.COMBO:
            return bucketLocalIdentifier !== BucketNames.VIEW;
        case VisualizationTypes.BULLET:
            return bucketLocalIdentifier !== BucketNames.VIEW;
        case VisualizationTypes.PUSHPIN:
        case VisualizationTypes.CHOROPLETH:
            return (bucketLocalIdentifier !== BucketNames.LOCATION &&
                bucketLocalIdentifier !== BucketNames.AREA &&
                bucketLocalIdentifier !== BucketNames.SEGMENT);
        case VisualizationTypes.SANKEY:
        case VisualizationTypes.DEPENDENCY_WHEEL:
            return bucketLocalIdentifier !== BucketNames.MEASURES;
        default:
            return false;
    }
}
export function setBucketTitles(referencePoint, visualizationType, intl) {
    const buckets = referencePoint?.buckets;
    const updatedUiConfig = cloneDeep(referencePoint?.uiConfig);
    if (!updatedUiConfig) {
        return updatedUiConfig;
    }
    buckets.forEach((bucket) => {
        const localIdentifier = bucket.localIdentifier ?? "";
        // skip disabled buckets
        if (!(updatedUiConfig?.buckets?.[localIdentifier]?.enabled ?? false)) {
            return;
        }
        if (bucketSupportsSubtitle(visualizationType, localIdentifier)) {
            const subtitleId = generateBucketSubtitleId(localIdentifier, visualizationType);
            const subtitle = getTranslation(subtitleId, intl);
            set(updatedUiConfig, [BUCKETS, localIdentifier, "subtitle"], subtitle);
        }
        const titleId = generateBucketTitleId(localIdentifier, visualizationType);
        const title = getTranslation(titleId, intl);
        set(updatedUiConfig, [BUCKETS, localIdentifier, "title"], title);
    });
    return updatedUiConfig;
}
export function generateBucketTitleId(localIdentifier, visualizationType) {
    return titles[`${localIdentifier}_${visualizationType}`]?.id;
}
function generateBucketSubtitleId(localIdentifier, visualizationType) {
    return subtitles[`${localIdentifier}_${visualizationType}`]?.id;
}
export function getItemsCount(buckets, localIdentifier) {
    return getBucketItems(buckets, localIdentifier).length;
}
export function getBucketItems(buckets, localIdentifier) {
    return buckets.find((bucket) => bucket.localIdentifier === localIdentifier)?.items ?? [];
}
// return bucket items matching localIdentifiers from any bucket
export function getItemsFromBuckets(buckets, localIdentifiers, types) {
    return localIdentifiers.reduce((bucketItems, localIdentifier) => {
        const toAdd = types
            ? getBucketItemsByType(buckets, localIdentifier, types)
            : getBucketItems(buckets, localIdentifier);
        bucketItems.push(...toAdd);
        return bucketItems;
    }, []);
}
export function getBucketItemsByType(buckets, localIdentifier, types) {
    const itemsOfType = [];
    const bucketItems = getBucketItems(buckets, localIdentifier);
    bucketItems.forEach((item) => {
        if (item.type && types.includes(item.type)) {
            itemsOfType.push(item);
        }
    });
    return itemsOfType;
}
export function getPreferredBucketItems(buckets, preference, type) {
    const bucket = getPreferredBucket(buckets, preference, type);
    return bucket?.items ?? [];
}
function getPreferredBucket(buckets, preference, type) {
    return preference.reduce((result, preference) => {
        if (result) {
            return result;
        }
        return buckets.find((bucket) => {
            const preferenceMatch = bucket.localIdentifier === preference;
            const typeMatch = (bucket?.items ?? []).every((item) => type.indexOf(item.type) !== -1);
            return preferenceMatch && typeMatch;
        });
    }, undefined);
}
function getAllBucketItemsByType(bucket, types) {
    return bucket.items.reduce((resultItems, item) => {
        if (item.type && types.includes(item.type)) {
            resultItems.push(item);
        }
        return resultItems;
    }, []);
}
export function getAllItemsByType(buckets, types) {
    return buckets.reduce((items, bucket) => {
        items.push(...getAllBucketItemsByType(bucket, types));
        return items;
    }, []);
}
export function removeDuplicateBucketItems(buckets) {
    const usedIdentifiersMap = {};
    return buckets.map((bucket) => {
        const filteredBucketItems = bucket.items.filter((bucketItem) => {
            const isDuplicate = usedIdentifiersMap[bucketItem.localIdentifier];
            usedIdentifiersMap[bucketItem.localIdentifier] = true;
            return !isDuplicate;
        });
        return filteredBucketItems.length === bucket.items.length
            ? bucket
            : {
                ...bucket,
                items: filteredBucketItems,
            };
    });
}
export function getTotalsFromBucket(buckets, bucketName) {
    const selectedBucket = buckets.find((bucket) => bucket.localIdentifier === bucketName);
    return selectedBucket?.totals ?? [];
}
function getUniqueAttributes(buckets) {
    const attributes = getAllItemsByType(buckets, [ATTRIBUTE, DATE]);
    return uniqBy(attributes, (attribute) => attribute?.attribute);
}
export function getMeasuresFromMdObject(insight) {
    if (!insight) {
        return [];
    }
    return bucketsMeasures(insightBuckets(insight), isSimpleMeasure);
}
export function getAllMeasures(buckets) {
    return getAllItemsByType(buckets, [METRIC]);
}
export function getFirstValidMeasure(buckets) {
    const measures = getAllMeasures(buckets);
    const validMeasures = measures.filter(isValidMeasure);
    return validMeasures[0] || null;
}
function isValidMeasure(measure) {
    if (isArithmeticBucketItem(measure)) {
        return measure.operandLocalIdentifiers.every((operandLocalIdentifier) => operandLocalIdentifier !== null);
    }
    return true;
}
export function getFirstAttribute(buckets) {
    return getUniqueAttributes(buckets)[0] || null;
}
export function getMeasureItems(buckets) {
    const preference = [BucketNames.MEASURES, BucketNames.SECONDARY_MEASURES, BucketNames.TERTIARY_MEASURES];
    const preferredMeasures = preference.reduce((acc, pref) => {
        const prefBucketItems = getPreferredBucketItems(buckets, [pref], [METRIC]);
        acc.push(...prefBucketItems);
        return acc;
    }, []);
    // if no preferred items are found, return all available items
    if (isEmpty(preferredMeasures)) {
        return getAllMeasures(buckets);
    }
    return preferredMeasures;
}
export function getBucketItemsWithExcludeByType(buckets, excludedBucket, type) {
    const includedBuckets = buckets.filter((bucket) => !excludedBucket.includes(bucket.localIdentifier));
    return getAllItemsByType(includedBuckets, type);
}
export function getStackItems(buckets, itemTypes = [ATTRIBUTE]) {
    const preferredStacks = getPreferredBucket(buckets, [BucketNames.STACK, BucketNames.SEGMENT], itemTypes);
    return preferredStacks?.items ?? [];
}
export function getAttributeToItems(buckets) {
    const preferredAttributeTos = getPreferredBucket(buckets, [BucketNames.ATTRIBUTE_TO], [ATTRIBUTE, DATE]);
    return preferredAttributeTos?.items ?? [];
}
export function getAttributeFromItems(buckets) {
    const preferredAttributeTos = getPreferredBucket(buckets, [BucketNames.ATTRIBUTE_FROM], [ATTRIBUTE, DATE]);
    return preferredAttributeTos?.items ?? [];
}
export function getViewItems(buckets, itemTypes = [ATTRIBUTE]) {
    const preferredStacks = getPreferredBucket(buckets, [BucketNames.VIEW], itemTypes);
    return preferredStacks?.items ?? [];
}
export function getAttributeItems(buckets) {
    return getAllAttributeItemsWithPreference(buckets, [
        BucketNames.LOCATION,
        BucketNames.VIEW,
        BucketNames.TREND,
    ]);
}
export function getAttributeItemsWithoutStacks(buckets, itemTypes = [ATTRIBUTE]) {
    return getAttributeItems(buckets).filter((attribute) => {
        return !getStackItems(buckets, itemTypes).includes(attribute);
    });
}
export function getAllCategoriesAttributeItems(buckets) {
    const stackItemsWithDate = getStackItems(buckets, [ATTRIBUTE, DATE]);
    return getAttributeItems(buckets).filter((attribute) => {
        return !stackItemsWithDate.includes(attribute);
    });
}
export function getAllAttributeItems(buckets) {
    return getAllItemsByType(buckets, [ATTRIBUTE, DATE]);
}
function getAllMeasureItems(buckets) {
    return getAllItemsByType(buckets, [METRIC]);
}
// get all attributes from buckets, but items from preferred buckets are first
export function getAllAttributeItemsWithPreference(buckets, preference) {
    const preferredAttributes = preference.reduce((acc, pref) => {
        const prefBucket = getPreferredBucket(buckets, [pref], [ATTRIBUTE, DATE]);
        if (prefBucket?.items?.length) {
            acc.push(...prefBucket.items);
        }
        return acc;
    }, []);
    const allBucketNames = buckets.map((bucket) => bucket?.localIdentifier);
    const otherBucketNames = allBucketNames.filter((bucketName) => !preference.includes(bucketName));
    const allOtherAttributes = otherBucketNames.reduce((attributes, bucketName) => {
        attributes.push(...getBucketItemsByType(buckets, bucketName, [ATTRIBUTE, DATE]));
        return attributes;
    }, []);
    return [...preferredAttributes, ...allOtherAttributes];
}
export function getDateItems(buckets) {
    return getAttributeItemsWithoutStacks(buckets).filter(isDateBucketItem);
}
export function getDateItemsWithMultipleDates(buckets) {
    return getAttributeItemsWithoutStacks(buckets, [ATTRIBUTE, DATE]).filter(isDateBucketItem);
}
export function getFistDateItemWithMultipleDates(buckets) {
    const dateItems = getDateItemsWithMultipleDates(buckets);
    return dateItems[0];
}
export function getFistDateItem(buckets) {
    const dateItems = getDateItems(buckets);
    return dateItems[0];
}
export function getMainDateItem(dateItems) {
    // first item for now, can be replaced by item matching the dimension of date filter in future
    return dateItems[0];
}
function hasItemsAboveLimit(bucket, itemsLimit) {
    const masterBucketItems = filterOutDerivedMeasures(bucket.items);
    return masterBucketItems.length > itemsLimit;
}
function applyItemsLimit(bucket, itemsLimit) {
    if (itemsLimit !== undefined && hasItemsAboveLimit(bucket, itemsLimit)) {
        const newBucket = cloneDeep(bucket);
        newBucket.items = newBucket.items.slice(0, itemsLimit);
        return newBucket;
    }
    return bucket;
}
function applyUiConfigOnBucket(bucket, bucketUiConfig) {
    return applyItemsLimit(bucket, bucketUiConfig?.itemsLimit);
}
export function applyUiConfig(referencePoint) {
    const buckets = referencePoint.buckets;
    const uiConfig = referencePoint.uiConfig?.buckets;
    const newBuckets = buckets.map((bucket) => applyUiConfigOnBucket(bucket, uiConfig?.[bucket.localIdentifier]));
    set(referencePoint, "buckets", newBuckets);
    return referencePoint;
}
export function hasBucket(buckets, localIdentifier) {
    return buckets.some((bucket) => bucket.localIdentifier === localIdentifier);
}
export function findBucket(buckets, localIdentifier) {
    return buckets.find((bucket) => bucket?.localIdentifier === localIdentifier);
}
export function getBucketsByNames(buckets, names) {
    return buckets.filter((bucket) => names.includes(bucket?.localIdentifier));
}
export function getFirstMasterWithDerived(measureItems) {
    const masters = filterOutDerivedMeasures(measureItems);
    const chosenMaster = masters[0];
    return measureItems.filter((measureItem) => measureItem.masterLocalIdentifier === chosenMaster.localIdentifier ||
        measureItem === chosenMaster);
}
export function removeAllArithmeticMeasuresFromDerived(extendedReferencePoint) {
    const originalBuckets = cloneDeep(extendedReferencePoint.buckets);
    extendedReferencePoint.buckets.forEach((bucket) => {
        bucket.items = filterOutArithmeticMeasuresFromDerived(bucket.items, originalBuckets);
    });
    return extendedReferencePoint;
}
export function removeAllDerivedMeasures(extendedReferencePoint) {
    extendedReferencePoint.buckets.forEach((bucket) => {
        bucket.items = filterOutDerivedMeasures(bucket.items);
    });
    return extendedReferencePoint;
}
export function findMasterBucketItem(derivedBucketItem, bucketItems) {
    return bucketItems.find((item) => item.localIdentifier === derivedBucketItem.masterLocalIdentifier);
}
export function findMasterBucketItems(bucketItems) {
    return bucketItems.filter((measure) => !isDerivedBucketItem(measure));
}
export function findDerivedBucketItems(masterBucketItem, bucketItems) {
    return bucketItems.filter((measure) => measure.masterLocalIdentifier === masterBucketItem.localIdentifier);
}
export function findDerivedBucketItem(masterBucketItem, bucketItems) {
    return bucketItems.find((bucketItem) => bucketItem.masterLocalIdentifier === masterBucketItem.localIdentifier);
}
export function hasDerivedBucketItems(masterBucketItem, buckets) {
    return buckets.some((bucket) => bucket.items.some((bucketItem) => bucketItem.masterLocalIdentifier === masterBucketItem.localIdentifier));
}
export function getFilteredMeasuresForStackedCharts(buckets) {
    const hasStacks = getStackItems(buckets, [ATTRIBUTE, DATE]).length > 0;
    if (hasStacks) {
        const limitedBuckets = limitNumberOfMeasuresInBuckets(buckets, 1);
        return getMeasureItems(limitedBuckets);
    }
    return getMeasureItems(buckets);
}
export function getFilteredMeasuresForStackedChartsWithStyleControlMetricSupport(buckets) {
    const hasStacks = getStackItems(buckets, [ATTRIBUTE, DATE]).length > 0;
    if (hasStacks) {
        const measureItems = getMeasureItems(buckets);
        const hasLineStyleControlMeasure = measureItems.some((measure) => measure.isThresholdMeasure);
        const limitedBuckets = limitNumberOfMeasuresInBuckets(buckets, hasLineStyleControlMeasure ? 2 : 1);
        return getMeasureItems(limitedBuckets);
    }
    return getMeasureItems(buckets);
}
export function noRowsAndHasOneMeasure(buckets) {
    const measuresBucket = bucketsFind(buckets, BucketNames.MEASURES);
    const measures = measuresBucket ? bucketItems(measuresBucket) : [];
    const rowsBucket = bucketsFind(buckets, BucketNames.VIEW);
    const rows = rowsBucket ? bucketItems(rowsBucket) : [];
    const hasOneMeasure = measures.length === 1;
    const hasRows = rows.length > 0;
    return Boolean(hasOneMeasure && !hasRows);
}
export function noColumnsAndHasOneMeasure(buckets) {
    const measuresBucket = bucketsFind(buckets, BucketNames.MEASURES);
    const measures = measuresBucket ? bucketItems(measuresBucket) : [];
    const columnsBucket = bucketsFind(buckets, BucketNames.STACK);
    const columns = columnsBucket ? bucketItems(columnsBucket) : [];
    const hasOneMeasure = measures.length === 1;
    const hasColumn = columns.length > 0;
    return hasOneMeasure && !hasColumn;
}
export function limitNumberOfMeasuresInBuckets(buckets, measuresLimitCount, tryToSelectDerivedWithMaster = false, filterMeasureFn = () => true) {
    const allMeasures = getAllMeasures(buckets);
    let selectedMeasures = [];
    // try to select measures one per bucket
    buckets.forEach((bucket) => {
        const currentBucketMeasures = getAllBucketItemsByType(bucket, [METRIC]).filter(filterMeasureFn);
        if (currentBucketMeasures.length === 0) {
            return;
        }
        selectedMeasures = getLimitedMeasures(currentBucketMeasures, 1, allMeasures, measuresLimitCount, tryToSelectDerivedWithMaster, selectedMeasures);
    });
    // if it was not possible to select all measures one per bucket then limit them globally
    if (selectedMeasures.length < measuresLimitCount) {
        selectedMeasures = getLimitedMeasures(allMeasures.filter(filterMeasureFn), measuresLimitCount, allMeasures, measuresLimitCount, tryToSelectDerivedWithMaster, selectedMeasures);
    }
    return pruneBucketMeasureItems(buckets, selectedMeasures);
}
function getLimitedMeasures(measures, measuresLimitCount, allMeasures, allMeasuresLimitCount, tryToSelectDerivedWithMaster, alreadySelectedMeasures) {
    let selectedMeasures = alreadySelectedMeasures;
    // try to select measures one by one together with their dependencies
    measures.forEach((measure) => {
        if (selectedMeasures.length - alreadySelectedMeasures.length === measuresLimitCount) {
            return;
        }
        const measureDependencies = getMeasureDependencies(measure, allMeasures);
        const measureWithDependencies = [measure, ...measureDependencies];
        if (tryToSelectDerivedWithMaster) {
            const derivedMeasures = findDerivedBucketItems(measure, allMeasures);
            const masterDerivedAndDependencies = [...measureWithDependencies, ...derivedMeasures];
            selectedMeasures = tryToSelectMeasures(masterDerivedAndDependencies, selectedMeasures, allMeasuresLimitCount);
        }
        selectedMeasures = tryToSelectMeasures(measureWithDependencies, selectedMeasures, allMeasuresLimitCount);
    });
    return selectedMeasures;
}
function findMeasureByLocalIdentifier(localIdentifier, measures) {
    return measures.find((measure) => measure.localIdentifier === localIdentifier);
}
function getMeasureDependencies(measure, allMeasures) {
    const directDependencies = [];
    if (measure.masterLocalIdentifier) {
        const masterMeasure = findMeasureByLocalIdentifier(measure.masterLocalIdentifier, allMeasures);
        if (masterMeasure !== undefined) {
            directDependencies.push(masterMeasure);
        }
    }
    if (measure.operandLocalIdentifiers) {
        measure.operandLocalIdentifiers
            .filter((operandLocalIdentifier) => operandLocalIdentifier !== null)
            .forEach((operandLocalIdentifier) => {
            const operandMeasure = findMeasureByLocalIdentifier(operandLocalIdentifier, allMeasures);
            if (operandMeasure !== undefined) {
                directDependencies.push(operandMeasure);
            }
        });
    }
    const indirectDependencies = [];
    directDependencies.forEach((directDependency) => {
        const dependenciesOfDependency = getMeasureDependencies(directDependency, allMeasures);
        indirectDependencies.push(...dependenciesOfDependency);
    });
    return uniqBy([...directDependencies, ...indirectDependencies], (dependency) => dependency.localIdentifier);
}
function tryToSelectMeasures(measures, alreadySelectedMeasures, limit) {
    const measuresToBePlaced = without(measures, ...alreadySelectedMeasures);
    if (measuresToBePlaced.length + alreadySelectedMeasures.length <= limit) {
        return [...alreadySelectedMeasures, ...measuresToBePlaced];
    }
    return alreadySelectedMeasures;
}
function pruneBucketMeasureItems(buckets, measuresToBeKept) {
    return buckets.map((bucket) => {
        const prunedItems = bucket.items.filter((item) => item.type !== METRIC ||
            measuresToBeKept.some((measureToBeKept) => measureToBeKept.localIdentifier === item.localIdentifier));
        return {
            ...bucket,
            items: prunedItems,
        };
    });
}
export function isShowOnSecondaryAxis(item) {
    return item?.showOnSecondaryAxis ?? false;
}
export function setMeasuresShowOnSecondaryAxis(items, value) {
    return items.map((item) => ({
        ...item,
        [SHOW_ON_SECONDARY_AXIS]: value,
    }));
}
export function removeShowOnSecondaryAxis(items) {
    return setMeasuresShowOnSecondaryAxis(items, null);
}
export function getAllMeasuresShowOnSecondaryAxis(buckets) {
    return getAllItemsByType(buckets, [METRIC]).filter(isShowOnSecondaryAxis);
}
export function getItemsLocalIdentifiers(items) {
    return items.map((item) => item?.localIdentifier ?? "");
}
export const transformMeasureBuckets = (measureBucketItemsLimits, buckets) => {
    let unusedMeasures = [];
    const newBuckets = measureBucketItemsLimits.map(({ localIdentifier, itemsLimit }) => {
        const preferredBucketLocalIdentifiers = localIdentifier === BucketNames.MEASURES
            ? [BucketNames.MEASURES, BucketNames.SIZE]
            : localIdentifier === BucketNames.SECONDARY_MEASURES
                ? [BucketNames.SECONDARY_MEASURES, BucketNames.COLOR]
                : [localIdentifier];
        const preferredBucketItems = getPreferredBucketItems(buckets, preferredBucketLocalIdentifiers, [
            METRIC,
        ]);
        const measuresToBePlaced = preferredBucketItems.splice(0, itemsLimit);
        if (measuresToBePlaced.length === 0) {
            return {
                localIdentifier,
                items: unusedMeasures.splice(0, itemsLimit),
            };
        }
        unusedMeasures = [...unusedMeasures, ...preferredBucketItems];
        return {
            localIdentifier,
            items: measuresToBePlaced,
        };
    });
    return newBuckets.map((bucket, bucketIndex) => {
        const bucketItemsLimit = measureBucketItemsLimits[bucketIndex].itemsLimit;
        const freeSlotsCount = bucketItemsLimit - bucket.items.length;
        if (freeSlotsCount === 0) {
            return bucket;
        }
        return {
            ...bucket,
            items: [...bucket.items, ...unusedMeasures.splice(0, freeSlotsCount)],
        };
    });
};
export const hasSameDateDimension = (dateItem, referenceDateItem) => {
    if (isDateBucketItem(dateItem) && isDateBucketItem(referenceDateItem)) {
        return areObjRefsEqual(dateItem.dateDatasetRef, referenceDateItem.dateDatasetRef);
    }
    return false;
};
export const removeDivergentDateItems = (viewItems, mainDateItem) => {
    return viewItems.filter((item) => isNotDateBucketItem(item) || hasSameDateDimension(item, mainDateItem));
};
const getDateFilterRef = (filters) => {
    const dateFilter = filters?.items?.find(isFiltersBucketItem);
    if (!dateFilter) {
        return undefined;
    }
    return dateFilter.dateDatasetRef;
};
export const isComparisonAvailable = (buckets, filters) => {
    const itemsFromBucket = buckets.reduce((acc, bucket) => {
        acc.push(...bucket.items);
        return acc;
    }, []);
    const bucketDateItems = itemsFromBucket.filter(isDateBucketItem);
    const areDateBucketItemsEmpty = bucketDateItems.length === 0;
    const dateFilterRef = getDateFilterRef(filters);
    if (areDateBucketItemsEmpty) {
        return true;
    }
    return bucketDateItems.some((bucketDateItem) => areObjRefsEqual(bucketDateItem.dateDatasetRef, dateFilterRef));
};
export const getMainRowAttribute = (buckets) => {
    const attributes = getBucketItems(buckets, BucketNames.ATTRIBUTE);
    const attributeItems = attributes.filter((item) => item.type === ATTRIBUTE);
    return attributeItems[0] ?? undefined;
};
export const cloneBucketItem = (item) => {
    const clonedItem = cloneDeep(item);
    clonedItem.localIdentifier = clonedItem.localIdentifier + "_cloned";
    return clonedItem;
};
const asLocalIdentifier = (measure) => measure.measure.localIdentifier;
/**
 * The function returns measures that should not have the chart fill (outline and pattern) applied.
 * @param insight - the insight to get the measures from
 * @param properties - the properties of the insight
 */
export function getChartFillIgnoredMeasureIdsFromMdObject(insight, properties) {
    if (!insight) {
        return [];
    }
    const insightType = insightVisualizationType(insight);
    // line chart series of combo chart must not have non-solid fill applied
    if (isComboChart(insightType)) {
        // each combo chart measure bucket can be set to represent a different chart (column, area, line)
        const lineChartBuckets = [
            ...(isLineChart(properties?.controls?.["primaryChartType"]) ? [BucketNames.MEASURES] : []),
            ...(isLineChart(properties?.controls?.["secondaryChartType"])
                ? [BucketNames.SECONDARY_MEASURES]
                : []),
        ];
        if (lineChartBuckets.length === 0) {
            return [];
        }
        return bucketsMeasures(insightBuckets(insight, ...lineChartBuckets)).map(asLocalIdentifier);
    }
    // bullet chart target series must not have non-solid fill applied
    if (isBulletChart(insightType)) {
        return bucketsMeasures(insightBuckets(insight, BucketNames.SECONDARY_MEASURES)).map(asLocalIdentifier);
    }
    return [];
}
//# sourceMappingURL=bucketHelper.js.map