"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVisibleTickSetsSelector = void 0;
exports.generateTicks = generateTicks;
const compute_axis_ticks_dimensions_1 = require("./compute_axis_ticks_dimensions");
const compute_series_domains_1 = require("./compute_series_domains");
const count_bars_in_cluster_1 = require("./count_bars_in_cluster");
const get_api_scale_configs_1 = require("./get_api_scale_configs");
const get_bar_paddings_1 = require("./get_bar_paddings");
const is_histogram_mode_enabled_1 = require("./is_histogram_mode_enabled");
const panel_utils_1 = require("../../../../common/panel_utils");
const scales_1 = require("../../../../scales");
const constants_1 = require("../../../../scales/constants");
const types_1 = require("../../../../scales/types");
const create_selector_1 = require("../../../../state/create_selector");
const compute_small_multiple_scales_1 = require("../../../../state/selectors/compute_small_multiple_scales");
const get_settings_spec_1 = require("../../../../state/selectors/get_settings_spec");
const canvas_text_bbox_calculator_1 = require("../../../../utils/bbox/canvas_text_bbox_calculator");
const common_1 = require("../../../../utils/common");
const multilayer_ticks_1 = require("../../axes/timeslip/multilayer_ticks");
const axis_type_utils_1 = require("../../utils/axis_type_utils");
const axis_utils_1 = require("../../utils/axis_utils");
const scales_2 = require("../../utils/scales");
const USE_ADAPTIVE_TICK_COUNT = true;
function axisMinMax(axisPosition, chartRotation, { width, height }) {
    const horizontal = (0, axis_type_utils_1.isHorizontalAxis)(axisPosition);
    const flipped = horizontal
        ? chartRotation === -90 || chartRotation === 180
        : chartRotation === 90 || chartRotation === 180;
    return horizontal ? [flipped ? width : 0, flipped ? 0 : width] : [flipped ? 0 : height, flipped ? height : 0];
}
function getDirectionFn({ type }) {
    return type === constants_1.ScaleType.Ordinal
        ? (label) => ((0, common_1.isRTLString)(label) ? 'rtl' : 'ltr')
        : () => 'ltr';
}
function generateTicks(scale, ticks, offset, labelFormatter, layer, detailedLayer, showGrid, multilayerTimeAxis) {
    const getDirection = getDirectionFn(scale);
    const isContinuous = (0, types_1.isContinuousScale)(scale);
    return ticks.reduce((acc, value, i) => {
        const domainClampedValue = isContinuous && typeof value === 'number' ? Math.max(value, scale.domain[0]) : value;
        const label = labelFormatter(value);
        const position = scale.scale(value) + offset;
        const domainClampedPosition = scale.scale(domainClampedValue) + offset;
        if (!(0, common_1.isFiniteNumber)(position) || !(0, common_1.isFiniteNumber)(domainClampedPosition))
            return acc;
        if (layer === 0 && i === 0 && position < domainClampedPosition)
            return acc;
        acc.push({
            value,
            domainClampedValue,
            label,
            position,
            domainClampedPosition,
            layer,
            detailedLayer,
            showGrid,
            direction: getDirection(label),
            multilayerTimeAxis,
        });
        return acc;
    }, []);
}
function getVisibleTicks(axisSpec, labelBox, totalBarsInCluster, labelFormatter, rotationOffset, scale, enableHistogramMode, layer, detailedLayer, ticks, adaptiveTickCount, multilayerTimeAxis = false, showGrid = true) {
    const isSingleValueScale = scale.domain[0] === scale.domain[1];
    const makeRaster = enableHistogramMode && scale.bandwidth > 0 && !multilayerTimeAxis;
    const ultimateTick = ticks.at(-1);
    const penultimateTick = ticks.at(-2);
    if (makeRaster && !isSingleValueScale && typeof penultimateTick === 'number' && typeof ultimateTick === 'number') {
        const computedTickDistance = ultimateTick - penultimateTick;
        const numTicks = scale.minInterval / (computedTickDistance || scale.minInterval);
        for (let i = 1; i <= numTicks; i++)
            ticks.push(i * computedTickDistance + ultimateTick);
    }
    const shift = totalBarsInCluster > 0 ? totalBarsInCluster : 1;
    const band = scale.bandwidth / (1 - scale.barsPadding);
    const halfPadding = (band - scale.bandwidth) / 2;
    const offset = (enableHistogramMode ? -halfPadding : (scale.bandwidth * shift) / 2) + (scale.isSingleValue() ? 0 : rotationOffset);
    const firstTickValue = ticks[0];
    const allTicks = makeRaster && isSingleValueScale && typeof firstTickValue === 'number'
        ? [
            {
                value: firstTickValue,
                domainClampedValue: firstTickValue,
                label: labelFormatter(firstTickValue),
                position: (scale.scale(firstTickValue) || 0) + offset,
                domainClampedPosition: (scale.scale(firstTickValue) || 0) + offset,
                layer: undefined,
                detailedLayer: 0,
                direction: 'rtl',
                showGrid,
                multilayerTimeAxis,
            },
            {
                value: firstTickValue + scale.minInterval,
                domainClampedValue: firstTickValue + scale.minInterval,
                label: labelFormatter(firstTickValue + scale.minInterval),
                position: scale.bandwidth + halfPadding * 2,
                domainClampedPosition: scale.bandwidth + halfPadding * 2,
                layer: undefined,
                detailedLayer: 0,
                direction: 'rtl',
                showGrid,
                multilayerTimeAxis,
            },
        ]
        : generateTicks(scale, ticks, offset, labelFormatter, layer, detailedLayer, showGrid, multilayerTimeAxis);
    const { showOverlappingTicks, showOverlappingLabels, position } = axisSpec;
    const requiredSpace = (0, axis_type_utils_1.isVerticalAxis)(position) ? labelBox.maxLabelBboxHeight / 2 : labelBox.maxLabelBboxWidth / 2;
    const bypassOverlapCheck = showOverlappingLabels || multilayerTimeAxis;
    return bypassOverlapCheck
        ? allTicks
        : allTicks
            .slice()
            .sort((a, b) => a.position - b.position)
            .reduce((prev, tick) => {
            const tickLabelFits = tick.position >= prev.occupiedSpace + requiredSpace;
            if (tickLabelFits || showOverlappingTicks) {
                prev.visibleTicks.push(tickLabelFits ? tick : { ...tick, label: '' });
                if (tickLabelFits)
                    prev.occupiedSpace = tick.position + requiredSpace;
            }
            else if (adaptiveTickCount && !tickLabelFits && !showOverlappingTicks) {
                prev.visibleTicks.push({ ...tick, label: '' });
            }
            return prev;
        }, { visibleTicks: [], occupiedSpace: -Infinity }).visibleTicks;
}
function getVisibleTickSet(scale, labelBox, { rotation: chartRotation }, axisSpec, groupCount, histogramMode, layer, detailedLayer, ticks, labelFormatter, adaptiveTickCount, multilayerTimeAxis = false, showGrid = true) {
    const vertical = (0, axis_type_utils_1.isVerticalAxis)(axisSpec.position);
    const somehowRotated = (vertical && chartRotation === -90) || (!vertical && chartRotation === 180);
    const rotationOffset = histogramMode && somehowRotated ? scale.step : 0;
    return getVisibleTicks(axisSpec, labelBox, groupCount, labelFormatter, rotationOffset, scale, histogramMode, layer, detailedLayer, ticks, adaptiveTickCount, multilayerTimeAxis, showGrid);
}
exports.getVisibleTickSetsSelector = (0, create_selector_1.createCustomCachedSelector)([
    get_settings_spec_1.getSettingsSpecSelector,
    get_api_scale_configs_1.getScaleConfigsFromSpecsSelector,
    compute_axis_ticks_dimensions_1.getJoinedVisibleAxesData,
    compute_series_domains_1.computeSeriesDomainsSelector,
    compute_small_multiple_scales_1.computeSmallMultipleScalesSelector,
    count_bars_in_cluster_1.countBarsInClusterSelector,
    is_histogram_mode_enabled_1.isHistogramModeEnabledSelector,
    get_bar_paddings_1.getBarPaddingsSelector,
], getVisibleTickSets);
function getVisibleTickSets({ rotation: chartRotation, locale, dow }, scaleConfigs, joinedAxesData, { xDomain, yDomains }, smScales, totalGroupsCount, enableHistogramMode, barsPadding) {
    return (0, canvas_text_bbox_calculator_1.withTextMeasure)((textMeasure) => {
        const panel = (0, panel_utils_1.getPanelSize)(smScales);
        return [...joinedAxesData].reduce((acc, [axisId, { axisSpec, axesStyle, gridLine, isXAxis, labelFormatter: userProvidedLabelFormatter }]) => {
            const { groupId, integersOnly, maximumFractionDigits: mfd, timeAxisLayerCount } = axisSpec;
            const yDomain = yDomains.find((yd) => yd.groupId === groupId);
            const domain = isXAxis ? xDomain : yDomain;
            const range = axisMinMax(axisSpec.position, chartRotation, panel);
            const maxTickCount = domain?.desiredTickCount ?? 0;
            const multilayerTimeAxis = (0, axis_utils_1.isMultilayerTimeAxis)(axisSpec, scaleConfigs.x.type, chartRotation);
            const maximumFractionDigits = mfd ?? (integersOnly ? 0 : undefined);
            const isNice = (isXAxis ? scaleConfigs.x.nice : scaleConfigs.y[groupId]?.nice) ?? false;
            const adaptiveTickCount = !isNice && USE_ADAPTIVE_TICK_COUNT;
            const getMeasuredTicks = (scale, ticks, layer, detailedLayer, labelFormatter, showGrid = true) => {
                const labelBox = (0, compute_axis_ticks_dimensions_1.getLabelBox)(axesStyle, ticks, labelFormatter, textMeasure, axisSpec, gridLine);
                return {
                    ticks: getVisibleTickSet(scale, labelBox, { rotation: chartRotation }, axisSpec, totalGroupsCount, enableHistogramMode, layer, detailedLayer, ticks, labelFormatter, adaptiveTickCount, multilayerTimeAxis, showGrid),
                    labelBox,
                    scale,
                };
            };
            const getScale = (desiredTickCount) => isXAxis
                ? (0, scales_2.computeXScale)({
                    xDomain: { ...xDomain, desiredTickCount },
                    totalBarsInCluster: totalGroupsCount,
                    range,
                    barsPadding,
                    enableHistogramMode,
                    maximumFractionDigits,
                })
                : yDomain &&
                    new scales_1.ScaleContinuous({ ...yDomain, range }, { ...yDomain, desiredTickCount, maximumFractionDigits });
            const fillLayer = (maxTickCountForLayer) => {
                let fallbackAskedTickCount = 2;
                let fallbackReceivedTickCount = Infinity;
                if (adaptiveTickCount) {
                    let previousActualTickCount = NaN;
                    for (let triedTickCount = maxTickCountForLayer; triedTickCount >= 1; triedTickCount--) {
                        const scale = getScale(triedTickCount);
                        const actualTickCount = scale?.ticks().length ?? 0;
                        if (!scale || actualTickCount === previousActualTickCount || actualTickCount < 2)
                            continue;
                        const raster = getMeasuredTicks(scale, scale.ticks(), undefined, 0, userProvidedLabelFormatter);
                        const nonZeroLengthTicks = raster.ticks.filter((tick) => tick.label.length > 0);
                        const uniqueLabels = new Set(raster.ticks.map((tick) => tick.label));
                        const areLabelsUnique = raster.ticks.length === uniqueLabels.size;
                        const areAdjacentTimeLabelsUnique = scale.type === constants_1.ScaleType.Time &&
                            !axisSpec.showDuplicatedTicks &&
                            (areLabelsUnique || raster.ticks.every((d, i, a) => i === 0 || d.label !== a[i - 1]?.label));
                        const atLeastTwoTicks = uniqueLabels.size >= 2;
                        const allTicksFit = !uniqueLabels.has('');
                        const compliant = axisSpec &&
                            (scale.type === constants_1.ScaleType.Time || atLeastTwoTicks) &&
                            (scale.type === constants_1.ScaleType.Log || allTicksFit) &&
                            ((scale.type === constants_1.ScaleType.Time && (axisSpec.showDuplicatedTicks || areAdjacentTimeLabelsUnique)) ||
                                (scale.type === constants_1.ScaleType.Log
                                    ? new Set(nonZeroLengthTicks.map((tick) => tick.label)).size === nonZeroLengthTicks.length
                                    : areLabelsUnique));
                        previousActualTickCount = actualTickCount;
                        if (raster && compliant) {
                            return {
                                entry: {
                                    ...raster,
                                    ticks: scale.type === constants_1.ScaleType.Log ? raster.ticks : nonZeroLengthTicks,
                                },
                                fallbackAskedTickCount,
                            };
                        }
                        else if (atLeastTwoTicks && uniqueLabels.size <= fallbackReceivedTickCount) {
                            fallbackReceivedTickCount = uniqueLabels.size;
                            fallbackAskedTickCount = triedTickCount;
                        }
                    }
                }
                return { fallbackAskedTickCount };
            };
            if (multilayerTimeAxis) {
                const scale = getScale(0);
                if (!scale || !(0, types_1.isContinuousScale)(scale))
                    throw new Error('Scale generation for the multilayer axis failed');
                return acc.set(axisId, (0, multilayer_ticks_1.multilayerAxisEntry)(xDomain, isXAxis && xDomain.isBandScale && enableHistogramMode, range, timeAxisLayerCount, scale, getMeasuredTicks, locale, dow));
            }
            const { fallbackAskedTickCount, entry } = fillLayer(maxTickCount);
            if (entry)
                return acc.set(axisId, entry);
            const scale = getScale(adaptiveTickCount ? fallbackAskedTickCount : maxTickCount);
            const lastResortCandidate = scale && getMeasuredTicks(scale, scale.ticks(), undefined, 0, userProvidedLabelFormatter);
            return lastResortCandidate ? acc.set(axisId, lastResortCandidate) : acc;
        }, new Map());
    });
}
//# sourceMappingURL=visible_ticks.js.map