"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeDateZoomTileTargets = exports.pruneDateZoomConfig = exports.copyDateZoomTileTargets = exports.normalizeGranularityParam = exports.resolveTileDateZoom = exports.getControlActiveGranularity = exports.getTileControl = exports.isEmptyDateZoomConfig = exports.normalizeDateZoomConfig = exports.EMPTY_DATE_ZOOM_CONFIG = exports.getChartZoomableFields = exports.getDateZoomXAxisFieldId = exports.getDateZoomCapabilities = exports.resolveToBaseTimeDimension = exports.getAllDimensionsMap = exports.resolveBaseDimension = exports.getTimeDimensionsMap = void 0;
const field_1 = require("../types/field");
const savedCharts_1 = require("../types/savedCharts");
const timeFrames_1 = require("../types/timeFrames");
const item_1 = require("./item");
const timeFrames_2 = require("./timeFrames");
/**
 * Collects all DATE and TIMESTAMP dimensions from an explore, keyed by item ID.
 */
const getTimeDimensionsMap = (explore) => {
    const map = {};
    for (const table of Object.values(explore.tables)) {
        for (const dim of Object.values(table.dimensions)) {
            if (dim.type === field_1.DimensionType.TIMESTAMP ||
                dim.type === field_1.DimensionType.DATE) {
                map[(0, item_1.getItemId)(dim)] = dim;
            }
        }
    }
    return map;
};
exports.getTimeDimensionsMap = getTimeDimensionsMap;
/**
 * Resolves a time-interval dimension (e.g. `order_date_month`) to its base
 * dimension (e.g. `order_date`). If the dimension has no `timeInterval` or the
 * base can't be parsed from the ID, returns the dimension itself.
 *
 * When the base dimension ID is resolved but not found in the map, returns
 * `undefined` — callers should handle this as an unexpected state.
 */
const resolveBaseDimension = (dimId, dim, timeDimensionsMap) => {
    const { baseDimensionId } = (0, timeFrames_2.getDateDimension)(dimId);
    if (dim.timeInterval && baseDimensionId) {
        return timeDimensionsMap[baseDimensionId];
    }
    return dim;
};
exports.resolveBaseDimension = resolveBaseDimension;
/**
 * Collects all dimensions from an explore, keyed by item ID.
 */
const getAllDimensionsMap = (explore) => {
    const map = {};
    for (const table of Object.values(explore.tables)) {
        for (const dim of Object.values(table.dimensions)) {
            map[(0, item_1.getItemId)(dim)] = dim;
        }
    }
    return map;
};
exports.getAllDimensionsMap = getAllDimensionsMap;
/**
 * Resolves any dimension from the metric query to its base time dimension.
 * Handles three cases:
 * - Standard time-interval dims (e.g. order_date_month) → resolves via getDateDimension
 * - Custom granularity dims (e.g. order_date_fiscal_quarter) → resolves via timeIntervalBaseDimensionName
 * - Plain date/timestamp dims → returns the dimension itself
 */
const resolveToBaseTimeDimension = (dimId, allDimensionsMap, timeDimensionsMap) => {
    const dim = allDimensionsMap[dimId];
    if (!dim)
        return undefined;
    // Custom granularity or non-date time-interval dimension (e.g. QUARTER_NAME
    // is STRING-typed) — resolve via timeIntervalBaseDimensionName
    if (dim.timeIntervalBaseDimensionName) {
        return timeDimensionsMap[(0, item_1.getItemId)({
            table: dim.table,
            name: dim.timeIntervalBaseDimensionName,
        })];
    }
    // Only process DATE/TIMESTAMP dimensions from here
    const timeDim = timeDimensionsMap[dimId];
    if (!timeDim)
        return undefined;
    return (0, exports.resolveBaseDimension)(dimId, timeDim, timeDimensionsMap);
};
exports.resolveToBaseTimeDimension = resolveToBaseTimeDimension;
const getDateZoomCapabilities = (explore, metricQuery) => {
    const allDimensions = Object.values(explore.tables).flatMap((t) => Object.values(t.dimensions));
    const allDimensionsMap = (0, exports.getAllDimensionsMap)(explore);
    const timeDimensionsMap = (0, exports.getTimeDimensionsMap)(explore);
    let hasTimestampDimension = false;
    let hasDateDimension = false;
    // hasDateDimension / hasTimestampDimension reflect the chart's own usage
    // (used to gate sub-day standard granularities on DATE-only dashboards),
    // so they stay scoped to dims referenced by the chart's metricQuery.
    for (const dimId of metricQuery.dimensions) {
        const baseDim = (0, exports.resolveToBaseTimeDimension)(dimId, allDimensionsMap, timeDimensionsMap);
        if (baseDim) {
            if (baseDim.type === field_1.DimensionType.TIMESTAMP) {
                hasTimestampDimension = true;
            }
            if (baseDim.type === field_1.DimensionType.DATE) {
                hasDateDimension = true;
            }
        }
    }
    // Custom granularities are sourced from any `customTimeInterval` dim in
    // the explore, not just siblings of the chart's currently-queried base.
    // The previous chart-query intersection here stripped configured customs
    // whenever a dashboard tile's query didn't currently reference the owning
    // base dim — and was also timing-sensitive, since metricQuery loads
    // asynchronously alongside the explore (PROD-7514, regression of
    // PROD-6788).
    const availableCustomGranularities = {};
    for (const dim of allDimensions) {
        if (dim.customTimeInterval) {
            availableCustomGranularities[dim.customTimeInterval] = dim.label;
        }
    }
    // Standard-granularity label overrides (project `granularity_labels`) are
    // surfaced under the same map keyed by the DateGranularity value, so the
    // date zoom renders them via getGranularityLabel. They are project-global,
    // so they apply regardless of which dims the dashboard tiles reference.
    for (const [tf, label] of Object.entries(explore.granularityLabels ?? {})) {
        const dateGranularity = timeFrames_1.timeFrameToDateGranularityMap[tf];
        if (dateGranularity) {
            availableCustomGranularities[dateGranularity] = label;
        }
    }
    return {
        availableCustomGranularities,
        hasTimestampDimension,
        hasDateDimension,
    };
};
exports.getDateZoomCapabilities = getDateZoomCapabilities;
/**
 * Resolves which field a chart's date zoom should target. For cartesian charts
 * whose x-axis resolves to a time dimension, that's the x-axis field — so
 * callers can pass it as `dateZoom.xAxisFieldId` and have the backend re-grain
 * the same field the chart actually plots, instead of auto-picking the first
 * date dimension in the query. Uses the same `resolveToBaseTimeDimension` check
 * the backend applies, so custom/string-typed interval x-axes (e.g. a
 * fiscal-quarter dimension) are targeted too. Returns undefined when the
 * backend's auto-pick should stand (non-cartesian charts, non-time x-axis, or
 * no x-axis).
 */
const getDateZoomXAxisFieldId = (chartConfig, explore) => {
    if (!explore || !(0, savedCharts_1.isCartesianChartConfig)(chartConfig.config)) {
        return undefined;
    }
    const xFieldId = chartConfig.config.layout?.xField;
    if (!xFieldId) {
        return undefined;
    }
    const baseTimeDimension = (0, exports.resolveToBaseTimeDimension)(xFieldId, (0, exports.getAllDimensionsMap)(explore), (0, exports.getTimeDimensionsMap)(explore));
    return baseTimeDimension ? xFieldId : undefined;
};
exports.getDateZoomXAxisFieldId = getDateZoomXAxisFieldId;
/**
 * The date fields a chart can actually be zoomed on: the dimensions in the
 * chart's own `metricQuery` that resolve to a base DATE/TIMESTAMP dimension.
 * This is the set a date-zoom control should offer for a tile, narrower than
 * the dashboard-filter field set, which exposes every filterable dimension in
 * the explore regardless of whether the chart plots it.
 *
 * The returned `fieldId` is the queried dimension id (e.g. `orders_order_date_month`),
 * matching the `xAxisFieldId` the backend re-grains in `_getDateZoomExplore`.
 */
const getChartZoomableFields = (explore, metricQuery) => {
    const allDimensionsMap = (0, exports.getAllDimensionsMap)(explore);
    const timeDimensionsMap = (0, exports.getTimeDimensionsMap)(explore);
    const fields = [];
    const seen = new Set();
    [...new Set(metricQuery.dimensions)].forEach((dimId) => {
        const dim = allDimensionsMap[dimId];
        if (!dim || seen.has(dimId))
            return;
        const baseTimeDimension = (0, exports.resolveToBaseTimeDimension)(dimId, allDimensionsMap, timeDimensionsMap);
        if (!baseTimeDimension)
            return;
        seen.add(dimId);
        fields.push({
            fieldId: dimId,
            label: dim.label ?? dimId,
            tableName: dim.table,
        });
    });
    return fields;
};
exports.getChartZoomableFields = getChartZoomableFields;
exports.EMPTY_DATE_ZOOM_CONFIG = {
    controls: [],
    tileTargets: {},
};
const normalizeDateZoomConfig = (config) => config?.dateZoomConfig ?? exports.EMPTY_DATE_ZOOM_CONFIG;
exports.normalizeDateZoomConfig = normalizeDateZoomConfig;
const isEmptyDateZoomConfig = (config) => config.controls.length === 0;
exports.isEmptyDateZoomConfig = isEmptyDateZoomConfig;
const getTileControl = (config, tileUuid) => {
    const target = config.tileTargets[tileUuid];
    return target
        ? config.controls.find((control) => control.uuid === target.controlUuid)
        : undefined;
};
exports.getTileControl = getTileControl;
const getControlActiveGranularity = (control, runtimeGranularities) => runtimeGranularities[control.uuid] ?? control.granularity;
exports.getControlActiveGranularity = getControlActiveGranularity;
const resolveTileDateZoom = ({ config, tileUuid, runtimeGranularities, globalGranularity, defaultXAxisFieldId, }) => {
    const target = config.tileTargets[tileUuid];
    const control = target
        ? config.controls.find((c) => c.uuid === target.controlUuid)
        : undefined;
    if (target && control) {
        const granularity = (0, exports.getControlActiveGranularity)(control, runtimeGranularities);
        // Param-only targets have no field to re-grain (fieldId null); the grain
        // only feeds the reserved `date_zoom` parameter.
        return {
            granularity,
            ...(target.fieldId ? { xAxisFieldId: target.fieldId } : {}),
        };
    }
    // Unassigned (or dangling target) -> Default = today's behavior.
    if (globalGranularity === undefined) {
        return undefined;
    }
    return {
        granularity: globalGranularity,
        ...(defaultXAxisFieldId ? { xAxisFieldId: defaultXAxisFieldId } : {}),
    };
};
exports.resolveTileDateZoom = resolveTileDateZoom;
// Match the existing global `?dateZoom` read: a standard grain round-trips to
// its canonical `DateGranularity` case; a custom-interval value is kept as-is.
const normalizeGranularityParam = (param) => Object.values(timeFrames_1.DateGranularity).find((grain) => grain.toLowerCase() === param.toLowerCase()) ?? param;
exports.normalizeGranularityParam = normalizeGranularityParam;
const copyDateZoomTileTargets = (config, mapping) => {
    const tileTargets = { ...config.tileTargets };
    let changed = false;
    mapping.forEach(({ fromTileUuid, toTileUuid }) => {
        const source = config.tileTargets[fromTileUuid];
        if (source) {
            tileTargets[toTileUuid] = { ...source };
            changed = true;
        }
    });
    return changed ? { ...config, tileTargets } : config;
};
exports.copyDateZoomTileTargets = copyDateZoomTileTargets;
const pruneDateZoomConfig = (config) => {
    const validControlUuids = new Set(config.controls.map((c) => c.uuid));
    const liveTargets = Object.entries(config.tileTargets).filter(([, target]) => validControlUuids.has(target.controlUuid));
    const referenced = new Set(liveTargets.map(([, target]) => target.controlUuid));
    return {
        controls: config.controls.filter((c) => referenced.has(c.uuid)),
        tileTargets: Object.fromEntries(liveTargets),
    };
};
exports.pruneDateZoomConfig = pruneDateZoomConfig;
/**
 * Drops the date-zoom targets for the given tiles (e.g. tiles deleted from the
 * dashboard) and prunes any control left with no targets. Returns the same
 * reference when none of the tiles had a target, so callers can skip a no-op
 * config update on dashboards that don't use controls.
 */
const removeDateZoomTileTargets = (config, tileUuids) => {
    const removed = new Set(tileUuids);
    if (![...removed].some((uuid) => config.tileTargets[uuid])) {
        return config;
    }
    return (0, exports.pruneDateZoomConfig)({
        ...config,
        tileTargets: Object.fromEntries(Object.entries(config.tileTargets).filter(([uuid]) => !removed.has(uuid))),
    });
};
exports.removeDateZoomTileTargets = removeDateZoomTileTargets;
//# sourceMappingURL=dateZoom.js.map