export var FilterType;
(function (FilterType) {
    FilterType["STRING"] = "string";
    FilterType["NUMBER"] = "number";
    FilterType["DATE"] = "date";
    FilterType["BOOLEAN"] = "boolean";
})(FilterType || (FilterType = {}));
export var FilterOperator;
(function (FilterOperator) {
    FilterOperator["NULL"] = "isNull";
    FilterOperator["NOT_NULL"] = "notNull";
    FilterOperator["EQUALS"] = "equals";
    FilterOperator["NOT_EQUALS"] = "notEquals";
    FilterOperator["STARTS_WITH"] = "startsWith";
    FilterOperator["ENDS_WITH"] = "endsWith";
    FilterOperator["INCLUDE"] = "include";
    FilterOperator["NOT_INCLUDE"] = "doesNotInclude";
    FilterOperator["LESS_THAN"] = "lessThan";
    FilterOperator["LESS_THAN_OR_EQUAL"] = "lessThanOrEqual";
    FilterOperator["GREATER_THAN"] = "greaterThan";
    FilterOperator["GREATER_THAN_OR_EQUAL"] = "greaterThanOrEqual";
    FilterOperator["IN_THE_PAST"] = "inThePast";
    FilterOperator["NOT_IN_THE_PAST"] = "notInThePast";
    FilterOperator["IN_THE_NEXT"] = "inTheNext";
    FilterOperator["IN_THE_CURRENT"] = "inTheCurrent";
    FilterOperator["NOT_IN_THE_CURRENT"] = "notInTheCurrent";
    FilterOperator["IN_BETWEEN"] = "inBetween";
    FilterOperator["NOT_IN_BETWEEN"] = "notInBetween";
    FilterOperator["IN_PERIOD_TO_DATE"] = "inPeriodToDate";
})(FilterOperator || (FilterOperator = {}));
/**
 * Date filter operators whose boundaries are relative to "now" and therefore
 * must be evaluated at query time, not baked in at compile time.
 */
const RELATIVE_DATE_FILTER_OPERATORS = new Set([
    FilterOperator.IN_THE_PAST,
    FilterOperator.NOT_IN_THE_PAST,
    FilterOperator.IN_THE_NEXT,
    FilterOperator.IN_THE_CURRENT,
    FilterOperator.NOT_IN_THE_CURRENT,
    FilterOperator.IN_PERIOD_TO_DATE,
]);
export const isRelativeDateFilterOperator = (operator) => RELATIVE_DATE_FILTER_OPERATORS.has(operator);
export var UnitOfTime;
(function (UnitOfTime) {
    UnitOfTime["milliseconds"] = "milliseconds";
    UnitOfTime["seconds"] = "seconds";
    UnitOfTime["minutes"] = "minutes";
    UnitOfTime["hours"] = "hours";
    UnitOfTime["days"] = "days";
    UnitOfTime["weeks"] = "weeks";
    UnitOfTime["months"] = "months";
    UnitOfTime["quarters"] = "quarters";
    UnitOfTime["years"] = "years";
})(UnitOfTime || (UnitOfTime = {}));
export const unitOfTimeFormat = {
    milliseconds: 'YYYY-MM-DD HH:mm:ss',
    seconds: 'YYYY-MM-DD HH:mm:ss',
    minutes: 'YYYY-MM-DD HH:mm',
    hours: 'YYYY-MM-DD HH',
    days: 'YYYY-MM-DD',
    weeks: 'YYYY-MM-DD',
    months: 'YYYY-MM',
    quarters: 'YYYY-MM',
    years: 'YYYY',
};
export const getUnitsOfTimeGreaterOrEqual = (unit) => {
    const unitsInOrder = [
        UnitOfTime.milliseconds,
        UnitOfTime.seconds,
        UnitOfTime.minutes,
        UnitOfTime.hours,
        UnitOfTime.days,
        UnitOfTime.weeks,
        UnitOfTime.months,
        UnitOfTime.quarters,
        UnitOfTime.years,
    ];
    const index = unitsInOrder.indexOf(unit);
    if (index === -1) {
        // return the original array if the unit is not found
        return unitsInOrder;
    }
    return unitsInOrder.slice(index);
};
export const isJoinModelRequiredFilter = (filter) => 'tableName' in filter.target;
export const isDashboardFieldTarget = (target) => target !== null &&
    typeof target === 'object' &&
    'fieldId' in target &&
    'tableName' in target;
export const isDateFilterRule = (filter) => 'unitOfTime' in (filter.settings || {}) ||
    'completed' in (filter.settings || {});
/* Utils */
export const isOrFilterGroup = (value) => 'or' in value;
export const isAndFilterGroup = (value) => 'and' in value;
export const isFilterGroup = (value) => isOrFilterGroup(value) || isAndFilterGroup(value);
export const isFilterRule = (value) => 'id' in value && 'target' in value && 'operator' in value;
export const isFilterTarget = (value) => !!value && typeof value === 'object' && 'fieldId' in value;
export const isMetricFilterTarget = (value) => !!value && typeof value === 'object' && 'fieldRef' in value;
export const flattenFilterGroup = (filterGroup) => {
    // Explicitly checking for undefined filter groups (and || or), saved filter group somehow was undefined when saving
    const groupItems = isAndFilterGroup(filterGroup)
        ? filterGroup.and
        : filterGroup.or;
    return (groupItems || []).flatMap((item) => {
        if (isFilterGroup(item)) {
            return flattenFilterGroup(item);
        }
        return [item];
    });
};
export const getFilterRules = (filters) => {
    const rules = [];
    if (filters.dimensions) {
        rules.push(...flattenFilterGroup(filters.dimensions));
    }
    if (filters.metrics) {
        rules.push(...flattenFilterGroup(filters.metrics));
    }
    if (filters.tableCalculations) {
        rules.push(...flattenFilterGroup(filters.tableCalculations));
    }
    return rules;
};
export const updateFieldIdInFilterGroupItem = (filterGroupItem, previousName, newName) => {
    if (isFilterGroup(filterGroupItem)) {
        // eslint-disable-next-line @typescript-eslint/no-use-before-define
        updateFieldIdInFilters(filterGroupItem, previousName, newName);
    }
    else if (filterGroupItem.target.fieldId === previousName) {
        // eslint-disable-next-line no-param-reassign
        filterGroupItem.target.fieldId = newName;
    }
};
export const updateFieldIdInFilters = (filterGroup, previousName, newName) => {
    if (filterGroup) {
        if (isOrFilterGroup(filterGroup)) {
            filterGroup.or.forEach((item) => updateFieldIdInFilterGroupItem(item, previousName, newName));
        }
        else if (isAndFilterGroup(filterGroup)) {
            filterGroup.and.forEach((item) => updateFieldIdInFilterGroupItem(item, previousName, newName));
        }
    }
};
export const removeFieldFromFilterGroup = (filterGroup, fieldId) => {
    if (!filterGroup) {
        return undefined;
    }
    const removeFiltersGroupItems = (items) => items.reduce((acc, item) => {
        if (isFilterGroup(item)) {
            const updatedGroup = removeFieldFromFilterGroup(item, fieldId); // remove field from filter groups recursively
            if (updatedGroup) {
                acc.push(updatedGroup);
            }
        }
        else if (item.target.fieldId !== fieldId) {
            // keep filter rule if fieldId does not match
            acc.push(item);
        }
        return acc;
    }, []);
    if (isOrFilterGroup(filterGroup)) {
        const updatedItems = removeFiltersGroupItems(filterGroup.or);
        if (updatedItems.length === 0) {
            return undefined;
        }
        return {
            ...filterGroup,
            or: updatedItems,
        };
    }
    const updatedItems = removeFiltersGroupItems(filterGroup.and);
    if (updatedItems.length === 0) {
        return undefined;
    }
    return {
        ...filterGroup,
        and: updatedItems,
    };
};
export const applyDimensionOverrides = (dashboardFilters, overrides) => {
    const overrideArray = overrides instanceof Array ? overrides : overrides.dimensions;
    const savedIds = new Set(dashboardFilters.dimensions.map((d) => d.id));
    // Track consumed overrides so each is applied to one saved filter and the
    // rest can be appended afterwards.
    const appliedOverrideIds = new Set();
    const overriddenDimensions = dashboardFilters.dimensions.map((dimension) => {
        // Match by id, then fall back to the target field so a shared link
        // keeps working after an edit rotates the filter's id.
        const override = overrideArray.find((o) => o.id === dimension.id) ??
            overrideArray.find((o) => !savedIds.has(o.id) &&
                !appliedOverrideIds.has(o.id) &&
                o.target.fieldId === dimension.target.fieldId &&
                o.target.tableName === dimension.target.tableName);
        if (override) {
            appliedOverrideIds.add(override.id);
            return {
                ...override,
                // The saved dashboard owns identity, tile targeting, lock
                // state and requirement flags; the override only carries
                // value/operator. Forcing the id re-homes a field-matched
                // override onto the saved filter.
                id: dimension.id,
                tileTargets: dimension.tileTargets,
                lockedTabUuids: dimension.lockedTabUuids,
                required: dimension.required,
                requiredGroupId: dimension.requiredGroupId,
            };
        }
        return dimension;
    });
    // Append overrides that matched no saved filter by id or field. Strip
    // requirement flags: like the matched branch above, overrides cannot
    // assert requirement metadata, so a stale or crafted rule can't inject
    // a required filter or satisfy a group.
    const newDimensions = overrideArray
        .filter((o) => !savedIds.has(o.id) && !appliedOverrideIds.has(o.id))
        .map((o) => ({
        ...o,
        required: undefined,
        requiredGroupId: undefined,
    }));
    overriddenDimensions.push(...newDimensions);
    return overriddenDimensions;
};
export const applyMetricOverrides = (dashboardFilters, overrides) => {
    const overrideArray = overrides instanceof Array ? overrides : overrides.metrics;
    // Match by id only; unmatched metric overrides are never appended.
    return dashboardFilters.metrics.map((metric) => {
        const override = overrideArray.find((o) => o.id === metric.id);
        if (override) {
            return {
                ...override,
                // Same ownership contract as applyDimensionOverrides: the
                // saved dashboard owns tile targeting, lock state and
                // requirement flags; the override only carries value/operator.
                tileTargets: metric.tileTargets,
                lockedTabUuids: metric.lockedTabUuids,
                required: metric.required,
                requiredGroupId: metric.requiredGroupId,
            };
        }
        return metric;
    });
};
export const isDashboardFilterRule = (value) => isFilterRule(value) && 'tableName' in value.target;
export var FilterGroupOperator;
(function (FilterGroupOperator) {
    FilterGroupOperator["and"] = "and";
    FilterGroupOperator["or"] = "or";
})(FilterGroupOperator || (FilterGroupOperator = {}));
export const convertDashboardFiltersToFilters = (dashboardFilters) => {
    const { dimensions, metrics, tableCalculations } = dashboardFilters;
    const filters = {};
    if (dimensions.length > 0) {
        filters.dimensions = {
            id: 'dashboard_dimension_filters',
            and: dimensions.map((dimension) => dimension),
        };
    }
    if (metrics.length > 0) {
        filters.metrics = {
            id: 'dashboard_dimension_metrics',
            and: metrics.map((metric) => metric),
        };
    }
    if (tableCalculations.length > 0) {
        filters.tableCalculations = {
            id: 'dashboard_tablecalculation_filters',
            and: tableCalculations.map((tableCalculation) => tableCalculation),
        };
    }
    return filters;
};
const isDashboardTileTargetFilterOverride = (filter) => typeof filter === 'object' || typeof filter === 'boolean';
export const convertDashboardFiltersParamToDashboardFilters = (dashboardFilters) => Object.entries(dashboardFilters).reduce((result, [key, value]) => ({
    ...result,
    [key]: value.map((f) => ({
        ...f,
        ...(f.tileTargets && {
            tileTargets: f.tileTargets.reduce((tileTargetsResult, tileTarget) => {
                const targetName = Object.keys(tileTarget)[0];
                const targetValue = Object.values(tileTarget)[0];
                if (isDashboardTileTargetFilterOverride(tileTarget)) {
                    return {
                        ...tileTargetsResult,
                        ...{ [targetName]: targetValue },
                    };
                }
                return tileTargetsResult;
            }, {}),
        }),
    })),
}), { dimensions: [], metrics: [], tableCalculations: [] });
export const compressDashboardFiltersToParam = (dashboardFilters) => Object.entries(dashboardFilters).reduce((result, [key, value]) => ({
    ...result,
    [key]: value.map((f) => ({
        ...f,
        ...(f.tileTargets && {
            tileTargets: Object.entries(f.tileTargets).reduce((tileTargetsResult, [tileTargetKey, tileTargetValue]) => {
                // If the filter is not disabled for this tile
                // AND the table and field match, we omit it.
                // The filter will be automatically applied there
                if (tileTargetValue !== false &&
                    isDashboardFieldTarget(tileTargetValue) &&
                    tileTargetValue.fieldId === f.target.fieldId &&
                    tileTargetValue.tableName === f.target.tableName) {
                    return tileTargetsResult;
                }
                return [
                    ...tileTargetsResult,
                    {
                        [tileTargetKey]: tileTargetValue,
                    },
                ];
            }, []),
        }),
    })),
}), { dimensions: [], metrics: [], tableCalculations: [] });
export const isFilterRuleDefinedForFieldId = (filterGroup, fieldId, isInterval = false) => {
    // Check if the filter group is an 'and' or 'or' group
    const filterGroupItems = isAndFilterGroup(filterGroup)
        ? filterGroup.and
        : filterGroup.or;
    // If the item is a filter rule, check if its id matches the provided filter rule id
    const isMatchingFieldId = (item) => {
        if (!isFilterGroup(item)) {
            // If the item is not a filter group, check if it matches the fieldId
            return isInterval
                ? item.target.fieldId.startsWith(fieldId)
                : item.target.fieldId === fieldId;
        }
        return false;
    };
    const isFilterRulePresent = (item) => {
        if (isMatchingFieldId(item)) {
            return true;
        }
        if (isFilterGroup(item)) {
            // If the item is a filter group, recursively check its items
            return isFilterRuleDefinedForFieldId(item, fieldId, isInterval);
        }
        return false;
    };
    // If the filter rule was not found in the filter group, return false
    return filterGroupItems.some(isFilterRulePresent);
};
//# sourceMappingURL=filter.js.map